LEE DONAHOE AND MICHAEL HARTL
TUTORIAL BY
TO BE DANGERO US
BASIC DEVELO PM EN T 03
RUBY ON RAILS
LEARN EN O UG H
2
Ruby on Rails Tutorial
Learn Web Development with Rails
Michael Hartl
ii
Contents
1 From zero to deploy 1
1.1 Up and running . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.1 Development environment . . . . . . . . . . . . . . . 8
1.1.2 Installing Rails . . . . . . . . . . . . . . . . . . . . . 16
1.2 The first application . . . . . . . . . . . . . . . . . . . . . . . 17
1.2.1 Bundler . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.2.2 rails server . . . . . . . . . . . . . . . . . . . . 27
1.2.3 Model-View-Controller (MVC) . . . . . . . . . . . . 34
1.2.4 Hello, world! . . . . . . . . . . . . . . . . . . . . . . 35
1.3 Version control with Git . . . . . . . . . . . . . . . . . . . . . 39
1.3.1 Installation and setup . . . . . . . . . . . . . . . . . . 42
1.3.2 What good does Git do you? . . . . . . . . . . . . . . 46
1.3.3 GitHub . . . . . . . . . . . . . . . . . . . . . . . . . 47
1.3.4 Branch, edit, commit, merge . . . . . . . . . . . . . . 52
1.4 Deploying . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
1.4.1 Heroku setup and deployment . . . . . . . . . . . . . 60
1.4.2 Heroku commands . . . . . . . . . . . . . . . . . . . 66
1.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
1.5.1 What we learned in this chapter . . . . . . . . . . . . 67
1.6 Conventions used in this book . . . . . . . . . . . . . . . . . 68
2 A toy app 71
2.1 Planning the application . . . . . . . . . . . . . . . . . . . . 72
2.1.1 A toy model for users . . . . . . . . . . . . . . . . . . 76
iii
iv CONTENTS
2.1.2 A toy model for microposts . . . . . . . . . . . . . . 77
2.2 The Users resource . . . . . . . . . . . . . . . . . . . . . . . 78
2.2.1 A user tour . . . . . . . . . . . . . . . . . . . . . . . 81
2.2.2 MVC in action . . . . . . . . . . . . . . . . . . . . . 89
2.2.3 Weaknesses of this Users resource . . . . . . . . . . . 97
2.3 The Microposts resource . . . . . . . . . . . . . . . . . . . . 97
2.3.1 A micropost microtour . . . . . . . . . . . . . . . . . 98
2.3.2 Putting the micro in microposts . . . . . . . . . . . . 104
2.3.3 A user has_many microposts . . . . . . . . . . . . . 106
2.3.4 Inheritance hierarchies . . . . . . . . . . . . . . . . . 109
2.3.5 Deploying the toy app . . . . . . . . . . . . . . . . . 115
2.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
2.4.1 What we learned in this chapter . . . . . . . . . . . . 120
3 Mostly static pages 121
3.1 Sample app setup . . . . . . . . . . . . . . . . . . . . . . . . 122
3.2 Static pages . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
3.2.1 Generated static pages . . . . . . . . . . . . . . . . . 131
3.2.2 Custom static pages . . . . . . . . . . . . . . . . . . . 140
3.3 Getting started with testing . . . . . . . . . . . . . . . . . . . 141
3.3.1 Our first test . . . . . . . . . . . . . . . . . . . . . . 146
3.3.2 Red . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
3.3.3 Green . . . . . . . . . . . . . . . . . . . . . . . . . . 149
3.3.4 Refactor . . . . . . . . . . . . . . . . . . . . . . . . . 152
3.4 Slightly dynamic pages . . . . . . . . . . . . . . . . . . . . . 154
3.4.1 Testing titles (Red) . . . . . . . . . . . . . . . . . . . 155
3.4.2 Adding page titles (Green) . . . . . . . . . . . . . . . 156
3.4.3 Layouts and embedded Ruby (Refactor) . . . . . . . . 161
3.4.4 Setting the root route . . . . . . . . . . . . . . . . . . 168
3.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
3.5.1 What we learned in this chapter . . . . . . . . . . . . 173
3.6 Advanced testing setup . . . . . . . . . . . . . . . . . . . . . 173
3.6.1 minitest reporters . . . . . . . . . . . . . . . . . . . . 174
3.6.2 Automated tests with Guard . . . . . . . . . . . . . . 174
CONTENTS v
4 Rails-flavored Ruby 179
4.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
4.1.1 Built-in helpers . . . . . . . . . . . . . . . . . . . . . 180
4.1.2 Custom helpers . . . . . . . . . . . . . . . . . . . . . 181
4.2 Strings and methods . . . . . . . . . . . . . . . . . . . . . . . 185
4.2.1 Strings . . . . . . . . . . . . . . . . . . . . . . . . . 187
4.2.2 Objects and message passing . . . . . . . . . . . . . . 191
4.2.3 Method definitions . . . . . . . . . . . . . . . . . . . 195
4.2.4 Back to the title helper . . . . . . . . . . . . . . . . . 197
4.3 Other data structures . . . . . . . . . . . . . . . . . . . . . . 198
4.3.1 Arrays and ranges . . . . . . . . . . . . . . . . . . . 198
4.3.2 Blocks . . . . . . . . . . . . . . . . . . . . . . . . . 203
4.3.3 Hashes and symbols . . . . . . . . . . . . . . . . . . 207
4.3.4 CSS revisited . . . . . . . . . . . . . . . . . . . . . . 212
4.4 Ruby classes . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
4.4.1 Constructors . . . . . . . . . . . . . . . . . . . . . . 215
4.4.2 Class inheritance . . . . . . . . . . . . . . . . . . . . 216
4.4.3 Modifying built-in classes . . . . . . . . . . . . . . . 221
4.4.4 A controller class . . . . . . . . . . . . . . . . . . . . 223
4.4.5 A user class . . . . . . . . . . . . . . . . . . . . . . . 226
4.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
4.5.1 What we learned in this chapter . . . . . . . . . . . . 230
5 Filling in the layout 231
5.1 Adding some structure . . . . . . . . . . . . . . . . . . . . . 232
5.1.1 Site navigation . . . . . . . . . . . . . . . . . . . . . 234
5.1.2 Bootstrap and custom CSS . . . . . . . . . . . . . . . 244
5.1.3 Partials . . . . . . . . . . . . . . . . . . . . . . . . . 253
5.2 Sass and the asset pipeline . . . . . . . . . . . . . . . . . . . 259
5.2.1 The asset pipeline . . . . . . . . . . . . . . . . . . . . 260
5.2.2 Syntactically awesome stylesheets . . . . . . . . . . . 263
5.3 Layout links . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
5.3.1 Contact page . . . . . . . . . . . . . . . . . . . . . . 271
5.3.2 Rails routes . . . . . . . . . . . . . . . . . . . . . . . 273
vi CONTENTS
5.3.3 Using named routes . . . . . . . . . . . . . . . . . . . 277
5.3.4 Layout link tests . . . . . . . . . . . . . . . . . . . . 278
5.4 User signup: A first step . . . . . . . . . . . . . . . . . . . . 284
5.4.1 Users controller . . . . . . . . . . . . . . . . . . . . . 284
5.4.2 Signup URL . . . . . . . . . . . . . . . . . . . . . . 286
5.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
5.5.1 What we learned in this chapter . . . . . . . . . . . . 291
6 Modeling users 293
6.1 User model . . . . . . . . . . . . . . . . . . . . . . . . . . . 294
6.1.1 Database migrations . . . . . . . . . . . . . . . . . . 296
6.1.2 The model file . . . . . . . . . . . . . . . . . . . . . 303
6.1.3 Creating user objects . . . . . . . . . . . . . . . . . . 304
6.1.4 Finding user objects . . . . . . . . . . . . . . . . . . 308
6.1.5 Updating user objects . . . . . . . . . . . . . . . . . . 310
6.2 User validations . . . . . . . . . . . . . . . . . . . . . . . . . 312
6.2.1 A validity test . . . . . . . . . . . . . . . . . . . . . . 313
6.2.2 Validating presence . . . . . . . . . . . . . . . . . . . 316
6.2.3 Length validation . . . . . . . . . . . . . . . . . . . . 320
6.2.4 Format validation . . . . . . . . . . . . . . . . . . . . 322
6.2.5 Uniqueness validation . . . . . . . . . . . . . . . . . 329
6.3 Adding a secure password . . . . . . . . . . . . . . . . . . . 339
6.3.1 A hashed password . . . . . . . . . . . . . . . . . . . 339
6.3.2 User has secure password . . . . . . . . . . . . . . . 342
6.3.3 Minimum password standards . . . . . . . . . . . . . 344
6.3.4 Creating and authenticating a user . . . . . . . . . . . 347
6.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
6.4.1 What we learned in this chapter . . . . . . . . . . . . 351
7 Sign up 353
7.1 Showing users . . . . . . . . . . . . . . . . . . . . . . . . . . 354
7.1.1 Debug and Rails environments . . . . . . . . . . . . . 354
7.1.2 A Users resource . . . . . . . . . . . . . . . . . . . . 362
7.1.3 Debugger . . . . . . . . . . . . . . . . . . . . . . . . 368
CONTENTS vii
7.1.4 A Gravatar image and a sidebar . . . . . . . . . . . . 372
7.2 Signup form . . . . . . . . . . . . . . . . . . . . . . . . . . . 380
7.2.1 Using form_with . . . . . . . . . . . . . . . . . . 382
7.2.2 Signup form HTML . . . . . . . . . . . . . . . . . . 385
7.3 Unsuccessful signups . . . . . . . . . . . . . . . . . . . . . . 390
7.3.1 A working form . . . . . . . . . . . . . . . . . . . . . 390
7.3.2 Strong parameters . . . . . . . . . . . . . . . . . . . 395
7.3.3 Signup error messages . . . . . . . . . . . . . . . . . 398
7.3.4 A test for invalid submission . . . . . . . . . . . . . . 404
7.4 Successful signups . . . . . . . . . . . . . . . . . . . . . . . 407
7.4.1 The finished signup form . . . . . . . . . . . . . . . . 409
7.4.2 The flash . . . . . . . . . . . . . . . . . . . . . . . . 411
7.4.3 The first signup . . . . . . . . . . . . . . . . . . . . . 415
7.4.4 A test for valid submission . . . . . . . . . . . . . . . 419
7.5 Professional-grade deployment . . . . . . . . . . . . . . . . . 422
7.5.1 SSL in production . . . . . . . . . . . . . . . . . . . 422
7.5.2 Production webserver . . . . . . . . . . . . . . . . . . 424
7.5.3 Production database configuration . . . . . . . . . . . 425
7.5.4 Production deployment . . . . . . . . . . . . . . . . . 426
7.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
7.6.1 What we learned in this chapter . . . . . . . . . . . . 429
8 Basic login 431
8.1 Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432
8.1.1 Sessions controller . . . . . . . . . . . . . . . . . . . 433
8.1.2 Login form . . . . . . . . . . . . . . . . . . . . . . . 436
8.1.3 Finding and authenticating a user . . . . . . . . . . . 441
8.1.4 Rendering with a flash message . . . . . . . . . . . . 446
8.1.5 A flash test . . . . . . . . . . . . . . . . . . . . . . . 449
8.2 Logging in . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
8.2.1 The log_in method . . . . . . . . . . . . . . . . . . 452
8.2.2 Current user . . . . . . . . . . . . . . . . . . . . . . . 455
8.2.3 Changing the layout links . . . . . . . . . . . . . . . 460
8.2.4 Testing layout changes . . . . . . . . . . . . . . . . . 477
viii CONTENTS
8.2.5 Login upon signup . . . . . . . . . . . . . . . . . . . 484
8.3 Logging out . . . . . . . . . . . . . . . . . . . . . . . . . . . 487
8.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 490
8.4.1 What we learned in this chapter . . . . . . . . . . . . 491
9 Advanced login 493
9.1 Remember me . . . . . . . . . . . . . . . . . . . . . . . . . . 493
9.1.1 Remember token and digest . . . . . . . . . . . . . . 494
9.1.2 Login with remembering . . . . . . . . . . . . . . . . 501
9.1.3 Forgetting users . . . . . . . . . . . . . . . . . . . . . 511
9.1.4 Two subtle bugs . . . . . . . . . . . . . . . . . . . . 514
9.2 “Remember me checkbox . . . . . . . . . . . . . . . . . . . 519
9.3 Remember tests . . . . . . . . . . . . . . . . . . . . . . . . . 526
9.3.1 Testing the “remember me” box . . . . . . . . . . . . 527
9.3.2 Testing the remember branch . . . . . . . . . . . . . . 533
9.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 537
9.4.1 What we learned in this chapter . . . . . . . . . . . . 537
10 Updating, showing, and deleting users 541
10.1 Updating users . . . . . . . . . . . . . . . . . . . . . . . . . 541
10.1.1 Edit form . . . . . . . . . . . . . . . . . . . . . . . . 542
10.1.2 Unsuccessful edits . . . . . . . . . . . . . . . . . . . 550
10.1.3 Testing unsuccessful edits . . . . . . . . . . . . . . . 553
10.1.4 Successful edits (with TDD) . . . . . . . . . . . . . . 554
10.2 Authorization . . . . . . . . . . . . . . . . . . . . . . . . . . 558
10.2.1 Requiring logged-in users . . . . . . . . . . . . . . . 560
10.2.2 Requiring the right user . . . . . . . . . . . . . . . . 566
10.2.3 Friendly forwarding . . . . . . . . . . . . . . . . . . 572
10.3 Showing all users . . . . . . . . . . . . . . . . . . . . . . . . 577
10.3.1 Users index . . . . . . . . . . . . . . . . . . . . . . . 577
10.3.2 Sample users . . . . . . . . . . . . . . . . . . . . . . 584
10.3.3 Pagination . . . . . . . . . . . . . . . . . . . . . . . 587
10.3.4 Users index test . . . . . . . . . . . . . . . . . . . . . 592
10.3.5 Partial refactoring . . . . . . . . . . . . . . . . . . . . 594
CONTENTS ix
10.4 Deleting users . . . . . . . . . . . . . . . . . . . . . . . . . . 596
10.4.1 Administrative users . . . . . . . . . . . . . . . . . . 597
10.4.2 The destroy action . . . . . . . . . . . . . . . . . . 602
10.4.3 User destroy tests . . . . . . . . . . . . . . . . . . . . 605
10.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 609
10.5.1 What we learned in this chapter . . . . . . . . . . . . 611
11 Account activation 613
11.1 Account activations resource . . . . . . . . . . . . . . . . . . 615
11.1.1 Account activations controller . . . . . . . . . . . . . 615
11.1.2 Account activation data model . . . . . . . . . . . . . 617
11.2 Account activation emails . . . . . . . . . . . . . . . . . . . . 624
11.2.1 Mailer templates . . . . . . . . . . . . . . . . . . . . 624
11.2.2 Email previews . . . . . . . . . . . . . . . . . . . . . 630
11.2.3 Email tests . . . . . . . . . . . . . . . . . . . . . . . 632
11.2.4 Updating the Users create action . . . . . . . . . . 637
11.3 Activating the account . . . . . . . . . . . . . . . . . . . . . 642
11.3.1 Generalizing the authenticated? method . . . . 642
11.3.2 Activation edit action . . . . . . . . . . . . . . . . 648
11.3.3 Activation test and refactoring . . . . . . . . . . . . . 651
11.4 Email in production . . . . . . . . . . . . . . . . . . . . . . . 659
11.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 663
11.5.1 What we learned in this chapter . . . . . . . . . . . . 663
12 Password reset 665
12.1 Password resets resource . . . . . . . . . . . . . . . . . . . . 669
12.1.1 Password resets controller . . . . . . . . . . . . . . . 670
12.1.2 New password resets . . . . . . . . . . . . . . . . . . 673
12.1.3 Password reset create action . . . . . . . . . . . . 676
12.2 Password reset emails . . . . . . . . . . . . . . . . . . . . . . 681
12.2.1 Password reset mailer and templates . . . . . . . . . . 681
12.2.2 Email tests . . . . . . . . . . . . . . . . . . . . . . . 688
12.3 Resetting the password . . . . . . . . . . . . . . . . . . . . . 689
12.3.1 Reset edit action . . . . . . . . . . . . . . . . . . . 690
x CONTENTS
12.3.2 Updating the reset . . . . . . . . . . . . . . . . . . . 694
12.3.3 Password reset test . . . . . . . . . . . . . . . . . . . 700
12.4 Email in production (take two) . . . . . . . . . . . . . . . . . 706
12.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 708
12.5.1 What we learned in this chapter . . . . . . . . . . . . 710
12.6 Proof of expiration comparison . . . . . . . . . . . . . . . . . 710
13 User microposts 713
13.1 A Micropost model . . . . . . . . . . . . . . . . . . . . . . . 713
13.1.1 The basic model . . . . . . . . . . . . . . . . . . . . 714
13.1.2 Micropost validations . . . . . . . . . . . . . . . . . . 717
13.1.3 User/Micropost associations . . . . . . . . . . . . . . 720
13.1.4 Micropost refinements . . . . . . . . . . . . . . . . . 725
13.2 Showing microposts . . . . . . . . . . . . . . . . . . . . . . . 731
13.2.1 Rendering microposts . . . . . . . . . . . . . . . . . 731
13.2.2 Sample microposts . . . . . . . . . . . . . . . . . . . 737
13.2.3 Profile micropost tests . . . . . . . . . . . . . . . . . 744
13.3 Manipulating microposts . . . . . . . . . . . . . . . . . . . . 747
13.3.1 Micropost access control . . . . . . . . . . . . . . . . 748
13.3.2 Creating microposts . . . . . . . . . . . . . . . . . . 752
13.3.3 A proto-feed . . . . . . . . . . . . . . . . . . . . . . 762
13.3.4 Destroying microposts . . . . . . . . . . . . . . . . . 773
13.3.5 Micropost tests . . . . . . . . . . . . . . . . . . . . . 780
13.4 Micropost images . . . . . . . . . . . . . . . . . . . . . . . . 784
13.4.1 Basic image upload . . . . . . . . . . . . . . . . . . . 784
13.4.2 Image validation . . . . . . . . . . . . . . . . . . . . 792
13.4.3 Image resizing . . . . . . . . . . . . . . . . . . . . . 799
13.4.4 Image upload in production . . . . . . . . . . . . . . 804
13.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 812
13.5.1 What we learned in this chapter . . . . . . . . . . . . 815
14 Following users 817
14.1 The Relationship model . . . . . . . . . . . . . . . . . . . . . 818
14.1.1 A problem with the data model (and a solution) . . . . 818
CONTENTS xi
14.1.2 User/relationship associations . . . . . . . . . . . . . 828
14.1.3 Relationship validations . . . . . . . . . . . . . . . . 831
14.1.4 Followed users . . . . . . . . . . . . . . . . . . . . . 833
14.1.5 Followers . . . . . . . . . . . . . . . . . . . . . . . . 837
14.2 A web interface for following users . . . . . . . . . . . . . . 840
14.2.1 Sample following data . . . . . . . . . . . . . . . . . 840
14.2.2 Stats and a follow form . . . . . . . . . . . . . . . . . 842
14.2.3 Following and followers pages . . . . . . . . . . . . . 854
14.2.4 A working follow button the standard way . . . . . . . 865
14.2.5 A working follow button with Ajax . . . . . . . . . . 870
14.2.6 Following tests . . . . . . . . . . . . . . . . . . . . . 875
14.3 The status feed . . . . . . . . . . . . . . . . . . . . . . . . . 877
14.3.1 Motivation and strategy . . . . . . . . . . . . . . . . 879
14.3.2 A rst feed implementation . . . . . . . . . . . . . . 881
14.3.3 Subselects . . . . . . . . . . . . . . . . . . . . . . . . 885
14.4 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . 891
14.4.1 Guide to further resources . . . . . . . . . . . . . . . 891
14.4.2 What we learned in this chapter . . . . . . . . . . . . 893
xii CONTENTS
Foreword
My former company (CD Baby) was one of the first to loudly switch to Ruby
on Rails, and then even more loudly switch back to PHP (Google me to read
about the drama). This book by Michael Hartl came so highly recommended
that I had to try it, and the Ruby on Rails Tutorial is what I used to switch back
to Rails again.
Though I’ve worked my way through many Rails books, this is the one that
finally made me “get” it. Everything is done very much “the Rails way”—a
way that felt very unnatural to me before, but now after doing this book finally
feels natural. This is also the only Rails book that does test-driven development
the entire time, an approach highly recommended by the experts but which has
never been so clearly demonstrated before. Finally, by including Git, GitHub,
and Heroku in the demo examples, the author really gives you a feel for what it’s
like to do a real-world project. The tutorial’s code examples are not in isolation.
The linear narrative is such a great format. Personally, I powered through
the Rails Tutorial in three long days doing all the examples and challenges at
the end of each chapter. [This is not typical! Most readers take much longer
to finish the tutorial. —Michael] Do it from start to finish, without jumping
around, and you’ll get the ultimate benefit.
Enjoy!
Derek Sivers (sivers.org)
Founder, CD Baby
xiii
xiv CONTENTS
Acknowledgments
The Ruby on Rails Tutorial owes a lot to my previous Rails book, RailsSpace,
and hence to my coauthor Aurelius Prochazka. I’d like to thank Aure both for
the work he did on that book and for his support of this one. I’d also like to thank
Debra Williams Cauley, my editor on both RailsSpace and the Ruby on Rails
Tutorial; as long as she keeps taking me to baseball games, I’ll keep writing
books for her.
I’d like to acknowledge a long list of Rubyists who have taught and
inspired me over the years: David Heinemeier Hansson, Yehuda Katz, Carl
Lerche, Jeremy Kemper, Xavier Noria, Ryan Bates, Geoffrey Grosenbach, Pe-
ter Cooper, Matt Aimonetti, Mark Bates, Gregg Pollack, Wayne E. Seguin,
Amy Hoy, Dave Chelimsky, Pat Maddox, Tom Preston-Werner, Chris Wan-
strath, Chad Fowler, Josh Susser, Obie Fernandez, Ian McFarland, Steph Bris-
tol, Pratik Naik, Sarah Mei, Sarah Allen, Wolfram Arnold, Alex Chaffee, Giles
Bowkett, Evan Dorn, Long Nguyen, James Lindenbaum, Adam Wiggins, Tikh-
on Bernstam, Ron Evans, Wyatt Greene, Miles Forrest, Sandi Metz, Ryan
Davis, Aaron Patterson, Aja Hammerly, Richard “Schneems” Schneeman, the
good people at Pivotal Labs, the Heroku gang, the thoughtbot folks, and the
GitHub crew.
I’d like to thank technical reviewer Andrew Thai for his careful reading of
the original manuscript and for his helpful suggestions. I’d also like to thank
my cofounders at Learn Enough, Nick Merwin and Lee Donahoe, for all their
help in preparing this tutorial.
Finally, many, many readers—far too many to list—have contributed a huge
number of bug reports and suggestions during the writing of this book, and I
gratefully acknowledge their help in making it as good as it can be.
xv
xvi CONTENTS
About the author
Michael Hartl is the creator of the Ruby on Rails Tutorial, one of the lead-
ing introductions to web development, and is cofounder and principal author
at LearnEnough.com. Previously, he was a physics instructor at the California
Institute of Technology (Caltech), where he received a Lifetime Achievement
Award for Excellence in Teaching. He is a graduate of Harvard College, has
a Ph.D. in Physics from Caltech, and is an alumnus of the Y Combinator en-
trepreneur program.
xvii
xviii CONTENTS
Copyright and license
Ruby on Rails Tutorial: Learn Web Development with Rails. Copyright © 2016
by Michael Hartl. Last updated 2020/01/27 19:05 PT.
All source code in the Ruby on Rails Tutorial is available jointly under the
MIT License and the Beerware License. Note that this means that either license
is valid; if you wish to use the software under the terms of the MIT license, there
is no need to buy me a beer as well.
The MIT License
Copyright (c) 2016 Michael Hartl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE BEERWARE LICENSE (Revision 42)
Michael Hartl wrote this code. As long as you retain this notice you can do
xix
xx CONTENTS
whatever you want with this stuff. If we meet some day, and you think this
stuff is worth it, you can buy me a beer in return.
Chapter 1
From zero to deploy
Welcome to the Ruby on Rails Tutorial! The purpose of this tutorial is to teach
you how to develop custom web applications. The resulting skillset will put you
in a great position to get a job as a web developer, start a career as a freelancer,
or found a company of your own. If you already know how to develop web
applications, this tutorial will quickly get you up to speed with Ruby on Rails.
The focus throughout the Ruby on Rails Tutorial is on general skills that are
useful no matter which specific technology you end up using. Once you under-
stand how web apps work, learning another framework can be done with much
less effort. That being said, the framework of choice in this tutorial—namely,
Ruby on Rails—has never been a better choice for learning web development
(Box 1.1).
Box 1.1. The many advantages of Rails
Ruby on Rails (or just “Rails” for short) is a free and open-source web devel-
opment framework written in the Ruby programming language. Upon its debut,
Ruby on Rails rapidly became one of the most popular tools for building dynamic
web applications. Rails is used by companies as varied as Airbnb, SoundCloud,
Disney, Hulu, GitHub, and Shopify, as well as by innumerable freelancers, inde-
pendent development shops, and startups.
1
2 CHAPTER 1. FROM ZERO TO DEPLOY
Although there are many choices in web development, Rails stands apart for its
elegance, power, and integrated approach to web applications. Using Rails, even
novice developers can build a full-stack web application without ever leaving the
framework—a huge boon for people learning web development for the first time.
Rails also gives you flexibility going forward—for example, serving as a great back
end if you want to build a single-page application or mobile app sometime down
the line.
One big advantage is that Rails is not prone to the “new hotness” problem that
plagues some development communities (notably JavaScript/Node.js), in which
a dizzyingly complex set of technologies seems to change every six months. As
Rails creator David Heinemeier Hansson once noted:
Back then the complexity merchant of choice was J2EE, but the com-
plaints are uncannily similar to those leveled against JavaScript to-
day… The core premise of Rails remains in many ways as controver-
sial today as it was when it premiered. That by formalizing conven-
tions, eliminating valueless choices, and offering a full-stack frame-
work that provides great defaults for anyone who wants to create a
complete application, we can make dramatic strides of productivity.
Due in part to this philosophy, Rails has remained so stable at its core much of this
tutorial has been the same since the third edition, launched in 2014. The things you
learn here won’t go out of date soon.
And yet, Rails continues to innovate. For example, the Rails 6 release includes
major new features for email routing, text formatting, parallel testing, and multiple-
database support. A big part of Rails 6 is being scalable by default”, which means
that Rails scales no matter how big your app gets. All this while maintaining rock-
solid dependability—indeed, the wildly popular developer platform GitHub, the
hugely successful online store-builder Shopify, and the collaboration tool (and very
first Rails app) Basecamp all run their sites on the pre-release versions of Rails.
This means that new versions of Rails are immediately tested by some of the largest,
most successful web apps in existence.
3
Not bad for a little side project cooked up by a freelance Danish web developer
way back in 2004. What was an edgy choice then is an easy choice now: with
its proven track-record, productive feature-set, and helpful community, Rails is a
fantastic framework for building modern web applications.
There are no formal prerequisites for this book, which contains integrated
tutorials for the Ruby programming language, the Unix command line, HTML,
CSS, a small amount of JavaScript, and even a little SQL. That’s a lot of mate-
rial to absorb, though, and if you’re new to software development I recommend
starting with the tutorials at Learn Enough, especially Learn Enough Command
Line to Be Dangerous and Learn Enough Ruby to Be Dangerous.
1
On the other
hand, a surprising number of complete beginners have gotten through this tuto-
rial, so don’t let me stop you if you’re excited to build web apps.
The principal teaching method of this tutorial is building real working soft-
ware through a series of example applications of increasing sophistication, start-
ing with a minimal hello app (Figure 1.1, Section 1.2), a slightly more capable
toy app (Figure 1.2, Chapter 2), and a real sample app (Figure 1.3, Chapter 3
through Chapter 14). As implied by their generic names, these applications fo-
cus on general principles, which are applicable to practically any kind of web
application. In particular, the full sample application includes all the major fea-
tures needed by professional-grade web apps, including user signup, login, and
account management. The final version of the sample app, developed in Chap-
ter 14, also bears more than a passing resemblance to Twitter—a website which,
coincidentally, was also originally written in Rails.
Let’s get started!
1
Adding the rest of the Learn Enough sequence would certainly provide excellent preparation for this tutorial,
but if you’re in a hurry you can probably get by with just Command Line and Ruby. Learn Enough Ruby to Be
Dangerous in particular has a chapter on building a simple web application using Sinatra, a Ruby-based micro-
framework that serves as excellent preparation for Rails. If you get stuck in the present tutorial, I suggest giving
Learn Enough Ruby to Be Dangerous and its prerequisites a try, then loop back here to see how it goes the second
time.
4 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.1: The beginning hello app.
5
Figure 1.2: An intermediate toy app.
6 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.3: The final sample app.
1.1. UP AND RUNNING 7
1.1 Up and running
One advantage of using this tutorial is that you can get up and running fast. In
particular, the Rails Tutorial has a long-running partnership with AWS Cloud9,
a development environment that runs in your browser. The result is a complete
system for developing all the software in this tutorial.
This is important because, even for experienced developers, installing Ruby,
Rails, and all the associated supporting software can be quite challenging. Com-
pounding the problem is the multiplicity of environments: different operating
systems, version numbers, preferences in text editors, etc.
This is why the recommended solution, especially for newer users, is to
sidestep most installation and configuration issues by using a cloud integrated
development environment, or cloud IDE (Section 1.1.1). The cloud IDE used
in this tutorial runs inside an ordinary web browser, and hence works the same
across different platforms. It also maintains the current state of your work, so
you can take a break from the tutorial and come back to the system just as you
left it.
A second possibility is to set up your native system (Windows, macOS, or
Linux) for Rails development. It is definitely recommended that you do this
eventually, but it can represent significant overhead, and is likely to require a
healthy amount of technical sophistication (Box 1.2). Instructions for setting
up your native system can be found in the Native OS setup section of Learn
Enough Dev Environment to Be Dangerous. (Note in particular that you’ll need
Ruby 2.6 or greater to run Rails 6.) If you go this route, be sure to complete the
configuration and Rails installation steps in Section 1.1.2 as well.
Box 1.2. Technical sophistication
The Ruby on Rails Tutorial is part of the Learn Enough family of tutorials,
which develop the theme of technical sophistication: the combination of hard and
soft skills that make it seem like you can magically solve any technical problem
(as illustrated in Tech Support Cheat Sheet from xkcd).
8 CHAPTER 1. FROM ZERO TO DEPLOY
Knowing how to code is an important component of technical sophistication,
but there’s more to it than that—you also have to know how to click around menu
items to learn the capabilities of a particular application, how to clarify a confusing
error message by googling it, or when to give up and just reboot the darn thing.
Because web applications have so many moving parts, they offer ample op-
portunities to develop your technical sophistication. In the context of Rails web
development, some specific examples of technical sophistication include making
sure you’re using the right Ruby gem versions, running bundle install or
bundle update, and restarting the local webserver if something doesn’t work.
(Don’t worry if all this sounds like gibberish; we’ll cover everything mentioned
here in the course of completing this tutorial.)
As you proceed through this tutorial, in all likelihood you will occasionally be
tripped up by things not immediately working as expected. Although some partic-
ularly tricky steps are explicitly highlighted in the text, it is impossible to anticipate
all the things that can go wrong. I recommend you embrace these inevitable stum-
bling blocks as opportunities to work on improving your technical sophistication.
Or, as we say in geek speak: It’s not a bug, it’s a feature!
1.1.1 Development environment
Considering various idiosyncratic customizations, there are probably as many
development environments as there are Rails programmers. To avoid this com-
plexity, the Ruby on Rails Tutorial standardizes on the excellent cloud develop-
ment environment Cloud9, part of Amazon Web Services (AWS). The resulting
workspace environment comes preconfigured with most of the software needed
for Ruby on Rails web development, including Ruby, RubyGems, and Git. (In-
deed, the only big piece of software we’ll install separately is Rails itself, and
this is intentional (Section 1.1.2).)
The cloud IDE includes the three essential components needed to develop
web applications: a command-line terminal, a filesystem navigator, and a text
editor (Figure 1.4). Among other features, the cloud IDE’s text editor supports
1.1. UP AND RUNNING 9
Figure 1.4: The anatomy of the cloud IDE.
the “Find in Files” global search that I consider essential to navigating any large
Ruby or Rails project. Finally, even if you decide not to use the cloud IDE
exclusively in real life (and I certainly recommend learning other tools as well),
it provides an excellent introduction to the general capabilities of command-line
terminals, text editor, and other development tools.
Here are the steps for getting started with the cloud development environ-
ment:
2
1. Because Cloud9 is part of Amazon Web Services (AWS), if you already
2
Due to the constantly evolving nature of sites like AWS, details may vary; use your technical sophistication
(Box 1.2) to resolve any discrepancies.
10 CHAPTER 1. FROM ZERO TO DEPLOY
have an AWS account you can just sign in.
3
To create a new Cloud9
workspace environment, go to the AWS console and type “Cloud9” in
the search box.
2. If you don’t already have an AWS account, you should sign up for a free
account at AWS Cloud9.
4
In order to prevent abuse, AWS requires a
valid credit card for signup, but the workspace is 100% free (for a year
as of this writing), and your card will not be charged. You might have to
wait up to 24 hours for the account to be activated, but in my case it was
ready in about ten minutes.
3. Once you’ve successfully gotten to the Cloud9 administrative page (Fig-
ure 1.5), clicking on “Create environment” and fill in the information
as shown in Figure 1.6, including the name “rails-tutorial”.
5
fill in the
description as shown in Figure 1.6. On the next page, choose Ubuntu
Server (not Amazon Linux) (Figure 1.7), and then click “Next step”.
clicking the confirmation buttons to accept the default settings until AWS
starts provisioning the IDE (Figure 1.9). You may run into a warning
message about being a “root” user, which you can safely ignore at this
early stage. (We’ll discuss the preferred but more complicated prac-
tice, called an Identity and Access Management (IAM) user, in Sec-
tion 13.4.4.)
Because using two spaces for indentation is a near-universal convention in
Ruby, I also recommend changing the editor to use two spaces instead of the
default four. As shown in Figure 1.10, you can do this by clicking the gear icon
in the upper right and then clicking the minus sign in the “Soft Tabs” setting
until it reaches 2. (Note that this takes effect immediately; you don’t need to
click a “Save” button.)
3
https://aws.amazon.com/
4
https://www.railstutorial.org/cloud9-signup
5
If you’ve previously done this tutorial, you may want to use a fresh environment, with a name like “rails-
tutorial-6”.
1.1. UP AND RUNNING 11
Figure 1.5: Creating an environment on AWS Cloud9.
Figure 1.6: Naming a new work environment at AWS Cloud9.
12 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.7: Selecting Ubuntu Server.
1.1. UP AND RUNNING 13
Figure 1.8: The final step before provisioning the IDE.
14 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.9: The default cloud IDE.
1.1. UP AND RUNNING 15
Figure 1.10: Setting Cloud9 to use two spaces for indentation.
16 CHAPTER 1. FROM ZERO TO DEPLOY
1.1.2 Installing Rails
The development environment from Section 1.1.1 includes all the software we
need to get started except for Rails itself. This is by design, as installing the
exact version of Rails used in this tutorial is important for getting predictable
results.
First, we’ll do a little preparation by adding configuration settings to pre-
vent the time-consuming installation of local Ruby documentation, as shown in
Listing 1.1.
6
Note that this step needs to be done only once per system. (For
more information on the command-line and other conventions in this book, see
Section 1.6.)
Listing 1.1: Configuring the .gemrc file to skip the installation of Ruby doc-
umentation.
$ echo "gem: --no-document" >> ~/.gemrc
To install Rails, we’ll use the gem command provided by the RubyGems
package manager, which involves typing the command shown in Listing 1.2
into your command-line terminal. (If developing on your local system, this
means using a regular terminal window; if using the cloud IDE, this means
using the command-line area shown in Figure 1.4.)
Listing 1.2: Installing Rails with a specific version number.
$ gem install rails -v 6.0.2.1
Here the -v flag ensures that the specified version of Rails gets installed. You
can confirm that the installation succeeded by passing the -v flag to the rails
command itself:
6
This uses the echo and >> (append) commands covered in Section 1.3 and Section 2.1 of Learn Enough
Command Line to Be Dangerous. Note that if the file being appended to doesn’t exist, >> is smart enough to
create it.
1.2. THE FIRST APPLICATION 17
$ rails -v
Rails 6.0.2.1
The version number output by this command should match the version installed
in Listing 1.2.
There’s one more configuration step, which is to install Yarn, a program to
manage software dependencies. If you’re using your native OS, you should fol-
low the Yarn installation instructions for your platform. If you’re on the cloud
IDE, you can run this command, which downloads and executes the necessary
commands from the Learn Enough CDN:
$ source <(curl -sL https://cdn.learnenough.com/yarn_install)
From time to time, you’ll probably get a warning message that looks like this:
========================================
Your Yarn packages are out of date!
Please run `yarn install --check-files` to update.
========================================
All you need to do if this happens is execute the suggested yarn command:
$ yarn install --check-files
That’s it! You’ve now got a system fully configured for Ruby on Rails web
development.
1.2 The first application
Following a long tradition in computer programming, our goal for the first appli-
cation is to write a hello, world” program. In particular, we will create a simple
application that displays the string “hello, world!” on a web page, both on our
development environment (Section 1.2.4) and on the live web (Section 1.4).
18 CHAPTER 1. FROM ZERO TO DEPLOY
Virtually all Rails applications start the same way, by running the rails
new command. This handy command creates a skeleton Rails application in a
directory of your choice. To get started, users not using the Cloud9 IDE rec-
ommended in Section 1.1.1 should make a environment directory for your
Rails projects if it doesn’t already exist (Listing 1.3) and then change into the
directory.
7
Listing 1.3: Making an environment directory for Rails projects.
# These steps are not needed on the cloud IDE.
$ cd # Change to the home directory.
$ mkdir environment # Make an environment directory.
$ cd environment/ # Change into the environment directory.
Listing 1.3 uses the Unix commands cd and mkdir; see Box 1.3 if you are not
already familiar with these commands.
Box 1.3. A crash course on the Unix command line
For readers coming from Windows or macOS, the Unix command line may
be unfamiliar. Luckily, if you are using the recommended cloud environment, you
automatically have access to a Unix (Linux) command line running a standard shell
(command-line interface) known as Bash.
The basic idea of the command line is simple: by issuing short commands, users
can perform a large number of operations, such as creating directories (mkdir),
moving and copying files (mv and cp), and navigating the filesystem by chang-
ing directories (cd). Although the command line may seem primitive to users
mainly familiar with graphical user interfaces (GUIs), appearances are deceiving:
the command line is one of the most powerful tools in the developers toolbox. In-
deed, you will rarely see the desktop of an experienced developer without several
open terminal windows running command-line shells.
7
This step is designed to unify the treatment of native systems and the cloud IDE by using identical directory
structures. If you are confident in your technical sophistication, feel free to omit this step, and use a directory of
your choice.
1.2. THE FIRST APPLICATION 19
Description Command Example
list contents ls $ ls -l
make directory mkdir <dirname> $ mkdir environment
change directory cd <dirname> $ cd environment/
cd one directory up $ cd ..
cd to home directory $ cd ~ or just $ cd
cd to path incl. home dir $ cd ~/environment/
move file (rename) mv <source> <target> $ mv foo bar
copy file cp <source> <target> $ cp foo bar
remove file rm <file> $ rm foo
remove empty directory rmdir <directory> $ rmdir environment/
remove nonempty directory rm -rf <directory> $ rm -rf tmp/
concatenate & display file contents cat <file> $ cat ~/.ssh/id_rsa.pub
Table 1.1: Some common Unix commands.
The general subject is deep, but for the purposes of this tutorial we will need
only a few of the most common Unix command-line commands, as summarized in
Table 1.1. For a more thorough introduction to the basics of the command line, see
the first Learn Enough tutorial, Learn Enough Command Line to Be Dangerous.
The next step on both local systems and the cloud IDE is to create the first
application using the command in Listing 1.4. Note that Listing 1.4 explicitly
includes the Rails version number as part of the command. This ensures that
the same version of Rails we installed in Listing 1.2 is used to create the first
application’s file structure.
Listing 1.4: Running rails new (with a specific version number).
$ cd ~/environment
$ rails _6.0.2.1_ new hello_app
create
create README.md
create Rakefile
create .ruby-version
create config.ru
create .gitignore
create Gemfile
run git init from "."
20 CHAPTER 1. FROM ZERO TO DEPLOY
Initialized empty Git repository in /home/ubuntu/environment/hello_app/.git/
create package.json
create app
create app/assets/config/manifest.js
create app/assets/stylesheets/application.css
create app/channels/application_cable/channel.rb
create app/channels/application_cable/connection.rb
create app/controllers/application_controller.rb
create app/helpers/application_helper.rb
.
.
.
Notice how many files and directories the rails command creates. This
standard directory and file structure (Figure 1.11) is one of the many advan-
tages of Rails: it immediately gets you from zero to a functional (if minimal)
application. Moreover, since the structure is common to all Rails apps, you can
immediately get your bearings when looking at someone else’s code.
A summary of the default Rails files appears in Table 1.2. We’ll learn about
most of these files and directories throughout the rest of this book. In particular,
starting in Section 5.2.1 we’ll discuss the app/assets directory, part of the
asset pipeline that makes it easy to organize and deploy assets such as Cascading
Style Sheets and image files.
1.2.1 Bundler
After creating a new Rails application, the next step is to use Bundler to install
and include the gems needed by the app. Bundler is run automatically (via
bundle install) by the rails command in Listing 1.4, but in this section
we’ll make some changes to the default application gems and run Bundler again.
This involves opening the Gemfile with a text editor. (With the cloud IDE,
this involves clicking the arrow in the file navigator to open the sample app
directory and double-clicking the Gemfile icon.) Although the exact version
numbers and details may differ slightly, the results should look something like
Figure 1.12 and Listing 1.5. (The code in this file is Ruby, but don’t worry at
this point about the syntax; Chapter 4 will cover Ruby in more depth.)
If the files and directories don’t appear as shown in Figure 1.12, click on
1.2. THE FIRST APPLICATION 21
Figure 1.11: The directory structure for a newly created Rails app.
22 CHAPTER 1. FROM ZERO TO DEPLOY
File/Directory Purpose
app/ Core application (app) code, including models, views, controllers, and helpers
app/assets Applications assets such as Cascading Style Sheets (CSS) and images
bin/ Binary executable files
config/ Application configuration
db/ Database files
doc/ Documentation for the application
lib/ Library modules
log/ Application log files
public/ Data accessible to the public (e.g., via web browsers), such as error pages
bin/rails A program for generating code, opening console sessions, or starting a local server
test/ Application tests
tmp/ Temporary files
README.md A brief description of the application
Gemfile Gem requirements for this app
Gemfile.lock A list of gems used to ensure that all copies of the app use the same gem versions
config.ru A configuration file for Rack middleware
.gitignore Patterns for files that should be ignored by Git
Table 1.2: A summary of the default Rails directory structure.
the file navigators gear icon and select “Refresh File Tree”. (As a general
rule, you should refresh the file tree any time files or directories don’t appear as
expected.)
8
Listing 1.5: The default Gemfile in the hello_app directory.
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.6.3'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.2.1'
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
# Use Puma as the app server
gem 'puma', '~> 3.11'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster.
8
This is a typical example of technical sophistication (Box 1.2).
1.2. THE FIRST APPLICATION 23
Figure 1.12: The default Gemfile open in a text editor.
24 CHAPTER 1. FROM ZERO TO DEPLOY
# Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Use Active Storage variant
# gem 'image_processing', '~> 1.2'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a
# debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
# Access an interactive console on exception pages or by calling 'console'
# anywhere in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in the
# background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
group :test do
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '>= 2.15'
gem 'selenium-webdriver'
# Easy installation and use of web drivers to run system tests with browsers
gem 'webdrivers'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Many of these lines are commented out with the hash symbol # (Section 4.2);
they are there to show you some commonly needed gems and to give examples
of the Bundler syntax. For now, we won’t need any gems other than the defaults.
Unless you specify a version number to the gem command, Bundler will
automatically install the latest requested version of the gem. This is the case,
1.2. THE FIRST APPLICATION 25
for example, in the code
gem 'sqlite3'
There are also two common ways to specify a gem version range, which allows
us to exert some control over the version used by Rails. The first looks like this:
gem 'capybara', '>= 2.15'
This installs the latest version of the capybara gem (which is used in test-
ing) as long as it’s greater than or equal to version 2.15—even if it’s, say,
version 7.2.
The second method looks like this:
gem 'rails', '~> 6.0.2.1'
This installs the gem rails as long as it’s version 6.0.2.1 or newer but
not 6.1 or newer. In other words, the >= notation always installs the latest
gem, whereas the ~> 6.0.2.1 notation will install 6.0.2 (if available) but
not 6.1.0.
9
Unfortunately, experience shows that even minor point releases can break
Rails applications, so for the Ruby on Rails Tutorial we’ll err on the side of
caution by including exact version numbers for all gems. You are welcome to
use the most up-to-date version of any gem, including using the ~> construction
in the Gemfile (which I generally recommend for more advanced users), but
be warned that this may cause the tutorial to act unpredictably.
Converting the Gemfile in Listing 1.5 to use exact gem versions results in
the code shown in Listing 1.6.
10
Note that we’ve also taken this opportunity to
9
Similarly, ~> 6.0 would install version 6.9 of a gem but not 7.0. This is especially useful if the project in
question uses semantic versioning (also called “semver”), which is a convention for numbering releases designed
to minimize the chances of breaking software dependencies.
10
You can determine the exact version number for each gem by running gem list <gem name> at the com-
mand line, but Listing 1.6 saves you the trouble.
26 CHAPTER 1. FROM ZERO TO DEPLOY
arrange for the sqlite3 gem to be included only in a development or test en-
vironment (Section 7.1.1), which prevents potential conflicts with the database
used by Heroku (Section 1.4). Finally, we’ve removed the line from Listing 1.5
specifying the exact Ruby version number; as noted in Section 7.5.4, it’s rec-
ommended to keep this line in a mission-critical app, but keeping it in a tutorial
of this nature introduces potential errors and complexity. (That said, if your app
fails to work without that line, you should definitely restore it.)
Important note: For all the Gemfiles in this book, you should use the
version numbers listed at gemfiles-6th-ed.railstutorial.org instead of the
ones listed below (although they should be identical if you are reading this
online).
Listing 1.6: A Gemfile with an explicit version for each Ruby gem.
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '6.0.2.1'
gem 'puma', '3.12.2'
gem 'sass-rails', '5.1.0'
gem 'webpacker', '4.0.7'
gem 'turbolinks', '5.2.0'
gem 'jbuilder', '2.9.1'
gem 'bootsnap', '1.4.5', require: false
group :development, :test do
gem 'sqlite3', '1.4.1'
gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
gem 'web-console', '4.0.1'
gem 'listen', '3.1.5'
gem 'spring', '2.1.0'
gem 'spring-watcher-listen', '2.0.1'
end
group :test do
gem 'capybara', '3.28.0'
gem 'selenium-webdriver', '3.142.4'
gem 'webdrivers', '4.1.2'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
1.2. THE FIRST APPLICATION 27
Once you’ve placed the contents of Listing 1.6 into the application’s Gem-
file, install the gems using bundle install:
11
$ cd hello_app/
$ bundle install
Fetching source index for https://rubygems.org/
.
.
.
The bundle install command might take a few moments, but when it’s
done our application will be ready to run.
By the way, when you run bundle install it’s possible that you’ll get
a message saying you need to run bundle update first. In this case you
should… run bundle update first! (Learning not to panic when things don’t
go exactly as planned is a key part of technical sophistication, and you’ll be
amazed at how often the “error” message contains the exact instructions you
need to fix the problem at hand.)
1.2.2 rails server
Thanks to running rails new in Section 1.2 and bundle install in Sec-
tion 1.2.1, we already have an application we can run—but how? Happily, Rails
comes with a command-line program, or script, that runs a local webserver to
assist us in developing our application: rails server.
Before running rails server, it’s necessary on some systems (including
the cloud IDE) to allow connections to the local web server. To enable this, you
should navigate to the file config/environments/development.rb and
paste in the two extra lines shown in Listing 1.7 and Figure 1.13.
Listing 1.7: Allowing connections to the local web server.
config/environments/development.rb
11
As noted in Table 3.1, you can even leave off install, as the bundle command by itself is an alias for
bundle install.
28 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.13: Allowing Cloud9 to connect to the Rails server.
Rails.application.configure do
.
.
.
# Allow connections to local server.
config.hosts.clear
end
The rails server command appears in Listing 1.8, which I recommend
you run in a second terminal tab so that you can still issue commands in the first
tab, as shown in Figure 1.14 and Figure 1.15. Note from Listing 1.8 that you
1.2. THE FIRST APPLICATION 29
Figure 1.14: Opening a new terminal tab.
can shut the server down using Ctrl-C.
12
Listing 1.8: Running the Rails server.
$ cd ~/environment/hello_app/
$ rails server
=> Booting Puma
=> Ctrl-C to shutdown server
12
Here “C” refers to the character on the keyboard, not the capital letter, so there’s no need to hold down the
Shift key to get a capital C”.
30 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.15: Running the Rails server in a separate tab.
1.2. THE FIRST APPLICATION 31
Figure 1.16: Sharing the local server running on the cloud workspace.
To view the result of rails server on a native OS, paste the URL
http://localhost:3000 into the address bar of your browser. On the cloud IDE,
go to Preview and click on Preview Running Application (Figure 1.16), and
then open it in a full browser window or tab (Figure 1.17). In either case, the
result should look something like Figure 1.18.
Exercises
The Ruby on Rails Tutorial contains a large number of exercises. Solving them
as you proceed through the tutorial is strongly recommended.
In order to keep the main discussion independent of the exercises, the so-
32 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.17: Opening the running app in a full browser window or tab.
1.2. THE FIRST APPLICATION 33
Figure 1.18: The default Rails page served by rails server.
34 CHAPTER 1. FROM ZERO TO DEPLOY
lutions are not generally incorporated into subsequent code listings. (In the
rare circumstance that an exercise solution is used subsequently, it is explic-
itly solved in the main text.) This means that over time your code may diverge
from the code shown in the tutorial due to differences introduced in the ex-
ercises. Learning how to resolve such discrepancies is a valuable exercise in
technical sophistication (Box 1.2).
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
Many of the exercises are challenging, but we’ll start out with some easy
ones just to get warmed up:
1. According to the default Rails page, what is the version of Ruby on your
system? Confirm by running ruby -v at the command line.
2. What is the version of Rails? Confirm that it matches the version installed
in Listing 1.2.
1.2.3 Model-View-Controller (MVC)
Even at this early stage, it’s helpful to get a high-level overview of how Rails
applications work, as illustrated in Figure 1.19. You might have noticed that the
standard Rails application structure (Figure 1.11) has an application directory
called app/, which includes subdirectories called models, views, and con-
trollers (among others). This is a hint that Rails follows the model-view-
controller (MVC) architectural pattern, which enforces a separation between the
data in the application (such as user information) and the code used to display
it, which is a common way of structuring a graphical user interface (GUI).
When interacting with a Rails application, a browser sends a request, which
is received by a webserver and passed on to a Rails controller, which is in charge
of what to do next. In some cases, the controller will immediately render a view,
which is a template that gets converted to HTML and sent back to the browser.
More commonly for dynamic sites, the controller interacts with a model, which
is a Ruby object that represents an element of the site (such as a user) and is in
1.2. THE FIRST APPLICATION 35
charge of communicating with the database. After invoking the model, the con-
troller then renders the view and returns the complete web page to the browser
as HTML.
If this discussion seems a bit abstract right now, don’t worry; we’ll cover
these ideas in more detail later in this book. In particular, Section 1.2.4 shows a
first tentative application of MVC, while Section 2.2.2 includes a more detailed
discussion of MVC in the context of the toy app. Finally, the full sample app
will use all aspects of MVC: we’ll cover controllers and views starting in Sec-
tion 3.2, models starting in Section 6.1, and we’ll see all three working together
in Section 7.1.2.
1.2.4 Hello, world!
As a first application of the MVC framework, we’ll make a wafer-thin change
to the rst app by adding a controller action to render the string “hello, world!”
to replace the default Rails page from Figure 1.18. (We’ll learn more about
controller actions starting in Section 2.2.2.)
As implied by their name, controller actions are defined inside controllers.
We’ll call our action hello and place it in the Application controller. Indeed,
at this point the Application controller is the only controller we have, which
you can verify by running
$ ls app/controllers/*_controller.rb
to view the current controllers. (We’ll start creating our own controllers in
Chapter 2.) Listing 1.9 shows the resulting definition of hello, which uses the
render function to return the HTML text “hello, world!”. (Don’t worry about
the Ruby syntax right now; it will be covered in more depth in Chapter 4.)
Listing 1.9: Adding a hello action to the Application controller.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
36 CHAPTER 1. FROM ZERO TO DEPLOY
Controller
Model
View
Database
Figure 1.19: A schematic representation of the model-view-controller (MVC)
architecture.
1.2. THE FIRST APPLICATION 37
def hello
render html: "hello, world!"
end
end
Having defined an action that returns the desired string, we need to tell Rails
to use that action instead of the default page in Figure 1.18. To do this, we’ll edit
the Rails router, which sits in front of the controller in Figure 1.19 and deter-
mines where to send requests that come in from the browser. (I’ve omitted the
router from Figure 1.19 for simplicity, but we’ll discuss it in more detail start-
ing in Section 2.2.2.) In particular, we want to change the default page, the root
route, which determines the page that is served on the root URL. Because it’s
the URL for an address like http://www.example.com/ (where nothing comes
after the final forward slash), the root URL is often referred to as / (“slash”) for
short.
As seen in Listing 1.10, the Rails routes file (config/routes.rb) in-
cludes a comment directing us to the Rails Guide on Routing, which includes
instructions on how to define the root route. The syntax looks like this:
root 'controller_name#action_name'
In the present case, the controller name is application and the action name
is hello, which results in the code shown in Listing 1.11.
Listing 1.10: The default routing file (formatted to fit).
config/routes.rb
Rails.application.routes.draw do
# For details on the DSL available within this file,
# see https://guides.rubyonrails.org/routing.html
end
Listing 1.11: Setting the root route.
config/routes.rb
38 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.20: Viewing “hello, world!” in the browser.
Rails.application.routes.draw do
root 'application#hello'
end
With the code from Listing 1.9 and Listing 1.11, the root route returns
“hello, world!” as required (Figure 1.20).
13
Hello, world!
13
The base URL for the Rails Tutorial Cloud9 shared URLs has changed from rails-tutorial-c9-mhartl.c9.io to
one on Amazon Web Services, but in many cases the screenshots are identical, so the browser address bar will
show old-style URLs in some figures (such as Figure 1.20). This is the sort of minor discrepancy you can resolve
using your technical sophistication (Box 1.2).
1.3. VERSION CONTROL WITH GIT 39
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Change the content of the hello action in Listing 1.9 to read “hola,
mundo!” instead of “hello, world!”.
2. Show that Rails supports non-ASCII characters by including an inverted
exclamation point, as in “¡Hola, mundo!” (Figure 1.21).
14
To get a ¡
character on a Mac, you can use Option-1; otherwise, you can always
copy-and-paste the character into your editor.
3. By following the example of the hello action in Listing 1.9, add a sec-
ond action called goodbye that renders the text “goodbye, world!”. Edit
the routes le from Listing 1.11 so that the root route goes to goodbye
instead of to hello (Figure 1.22).
1.3 Version control with Git
Now that we have a working “hello, world” application, we’ll take a moment
for a step that, while technically optional, would be viewed by many expe-
rienced software developers as practically essential: placing our application
source code under version control. Version control systems allow us to track
changes to our project’s code, collaborate more easily, and roll back any in-
advertent errors (such as accidentally deleting files). Knowing how to use a
version control system is a required skill for every professional-grade software
developer.
There are many options for version control, but the software development
community has largely standardized on Git, a distributed version control sys-
tem originally developed by Linus Torvalds to host the Linux kernel. Git is a
14
Your editor may display a message like “invalid multibyte character”, but this is not a cause for concern. You
can Google the error message if you want to learn how to make it go away.
40 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.21: Changing the root route to return “¡Hola, mundo!”.
1.3. VERSION CONTROL WITH GIT 41
Figure 1.22: Changing the root route to return “goodbye, world!”.
42 CHAPTER 1. FROM ZERO TO DEPLOY
large subject, and we’ll only be scratching the surface in this book; for a more
thorough introduction to the basics, see Learn Enough Git to Be Dangerous.
Putting your source code under version control with Git is strongly recom-
mended, not only because it’s nearly a universal practice in the Rails world,
but also because it will allow you to back up and share your code more eas-
ily (Section 1.3.3) and deploy your application right here in the first chapter
(Section 1.4).
1.3.1 Installation and setup
The cloud IDE recommended in Section 1.1.1 includes Git by default, so no
installation is necessary in this case. Otherwise, Learn Enough Git to Be Dan-
gerous includes instructions for installing Git on your system.
First-time system setup
Before using Git, you should perform a few one-time setup steps. These are
system setups, meaning you have to do them only once per computer.
The first (and required) step is to configure your name and email address,
as shown in Listing 1.12.
Listing 1.12: Configuring the name and email fields for Git.
$ git config --global user.name "Your Name"
$ git config --global user.email your.email@example.com
Note that the name and email address you use in your Git configuration will be
available in any repositories you make public.
If you’re using the cloud IDE, the next step is to configure a default editor
for the times when Git needs one (such as editing, or amending changes to
projects). We’ll use the nano editor, which is relatively friendly to beginners
and is the default on the cloud IDE. As of this writing, the default editor gets
reset on logout, and the path is also incorrect, so we need to execute Listing 1.13,
which creates a symbolic link (or “symlink”) to the correct location of the nano
1.3. VERSION CONTROL WITH GIT 43
executable.
15
(The command in Listing 1.13 is a little advanced, so certainly
don’t worry about understanding it if it looks confusing.)
Listing 1.13: Configuring the default editor on the cloud IDE.
$ sudo ln -sf `which nano` /usr/bin
Next, we’ll take an optional but convenient step and set up an alias, or syn-
onym, for the commonly used checkout command, as shown in Listing 1.14.
Listing 1.14: Setting up git co as a checkout alias.
$ git config --global alias.co checkout
In this tutorial, I’ll always use the full git checkout command for maximum
compatibility, but in practice I almost always use git co for short.
The final step is to prevent Git from asking for your password every time you
want to use commands like push or pull (Section 1.3.4). The options for doing
this are system-dependent; see the article Caching your GitHub password in
Git if you’re using anything other than Linux (including the cloud IDE). If you
are using Linux (including of course the cloud IDE), you can simply set a cache
timeout as shown in Listing 1.15.
Listing 1.15: Configuring Git to remember passwords for a set length of time.
$ git config --global credential.helper "cache --timeout=86400"
Listing 1.15 configures Git to remember any passwords you use for 86,400 sec-
onds (one day).
16
If you’re highly security-conscious, you can use a shorter
timeout, such as the default 900 seconds, or 15 minutes.
15
Vim is actually my Git preferred editor in this context, and is recommended for people who have Minimum
Viable Vim™ or better (as described in Learn Enough Text Editor to Be Dangerous). To use vim in Listing 1.13,
just replace `which nano` with `which vim`
16
In theory, you could use a longer timeout, but on the cloud IDE the timer seems to gets reset every day or so,
so entering a timeout of more than 86,400 seconds appears to have little effect in this case.
44 CHAPTER 1. FROM ZERO TO DEPLOY
First-time repository setup
Now we come to some steps that are necessary each time you create a new
repository (sometimes called a repo for short). The first step is to navigate to
the root directory of the hello app and initialize a new repository:
$ cd ~/environment/hello_app # Just in case you weren't already there
$ git init
Reinitialized existing Git repository in
/home/ubuntu/environment/hello_app/.git/
Note that Git outputs a message that the repository has been reinitialized. This
is because, as of Rails 6, running rails new (Listing 1.4) automatically ini-
tializes a Git repository (a strong indication of how ubiquitous Git’s use is in
tech). Thus, the git init step isn’t technically necessary in our case, but this
won’t hold for general Git repositories, so always running git init is a good
habit to cultivate.
The next step is to add all the project les to the repository using git add
-A:
17
$ git add -A
This command adds all the files in the current directory apart from those that
match the patterns in a special file called .gitignore. The rails new com-
mand automatically generates a .gitignore file appropriate to a Rails project,
but you can add additional patterns as well.
18
The added files are initially placed in a staging area, which contains pending
changes to our project. We can see which files are in the staging area using the
status command:
17
Many developers use the nearly equivalent git add ., where . (“dot”) represents the current directory. In
the rare cases where the two differ, what you usually want is git add -A, and this is what’s used in the official
Git documentation, so that’s what we go with here.
18
Although we’ll never need to edit it in the main tutorial, an example of adding a rule to the .gitignore file
appears in Section 3.6.2, which is part of the optional advanced testing setup in Section 3.6.
1.3. VERSION CONTROL WITH GIT 45
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .browserslistrc
new file: .gitignore
new file: .ruby-version
new file: Gemfile
new file: Gemfile.lock
.
.
.
To tell Git we want to keep the changes, we use the commit command:
$ git commit -m "Initialize repository"
[master (root-commit) df0a62f] Initialize repository
.
.
.
The -m flag lets us add a message for the commit; if we omit -m, Git will open
the systems default editor and have us enter the message there. (All the exam-
ples in this tutorial will use the -m flag.)
It is important to note that Git commits are local, recorded only on the ma-
chine on which the commits occur. We’ll see how to push the changes up to a
remote repository (using git push) in Section 1.3.4.
By the way, we can see a list of the commit messages using the log com-
mand:
$ git log
commit b981e5714e4d4a4f518aeca90270843c178b714e (HEAD -> master)
Author: Michael Hartl <michael@michaelhartl.com>
Date: Sun Aug 18 17:57:06 2019 +0000
Initialize repository
46 CHAPTER 1. FROM ZERO TO DEPLOY
Depending on the length of the repository’s log history, you may have to type
q to quit. (As explained in Learn Enough Git to Be Dangerous, git log uses
the less interface covered in Learn Enough Command Line to Be Dangerous.)
1.3.2 What good does Git do you?
If you’ve never used version control before, it may not be entirely clear at
this point what good it does you, so let’s look at just one example. Suppose
you’ve made some accidental changes, such as (D’oh!) deleting the critical
app/controllers/ directory.
$ ls app/controllers/
application_controller.rb concerns/
$ rm -rf app/controllers/
$ ls app/controllers/
ls: app/controllers/: No such file or directory
Here we’re using the Unix ls command to list the contents of the app/con-
trollers/ directory and the rm command to remove it (Table 1.1). As noted
in Learn Enough Command Line to Be Dangerous, the -rf flag means “recur-
sive force”, which recursively removes all files, directories, subdirectories, and
so on, without asking for explicit confirmation of each deletion.
Let’s check the status to see what changed:
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: app/controllers/application_controller.rb
deleted: app/controllers/concerns/.keep
no changes added to commit (use "git add" and/or "git commit -a")
We see here that a le has been deleted, but the changes are only on the “work-
ing tree”; they haven’t been committed yet. This means we can still undo the
1.3. VERSION CONTROL WITH GIT 47
changes using the checkout command with the -f flag to force overwriting
the current changes:
$ git checkout -f
$ git status
On branch master
nothing to commit, working tree clean
$ ls app/controllers/
application_controller.rb concerns/
The missing files and directories are back. That’s a relief!
1.3.3 GitHub
Now that we’ve put our project under version control with Git, it’s time to push
our code up to GitHub, a site optimized for hosting and sharing Git reposito-
ries.
19
Putting a copy of your Git repository at GitHub serves two purposes: it’s
a full backup of your code (including the full history of commits), and it makes
any future collaboration much easier.
Getting started with GitHub is straightforward: just sign up for a GitHub
account if you don’t already have one (Figure 1.23).
Once you’ve signed up or signed in, click on the + sign dropdown menu
and select “New repository” (Figure 1.24).
On the new repository page, fill the fields with the repository name
(hello_app) and optional description, and take special care to select the “Pri-
vate” option, as shown in Figure 1.25. Although Rails apps are in principle
safe to expose as public repositories, so many things can go wrong (such as ac-
cidentally exposing passwords or private keys) that making all such repositories
private is a prudent default.
20
After clicking the “Create repository” button, you should see something
like Figure 1.26, with commands for adding an existing repository to GitHub.
19
Bitbucket and GitLab are also excellent choices. Like GitHub, GitLab is written in Rails.
20
GitHub allows unlimited public and private repositories.
48 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.23: Signing up for GitHub.
1.3. VERSION CONTROL WITH GIT 49
Figure 1.24: Selecting the “New repository” option.
50 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.25: Creating a private repository at GitHub.
1.3. VERSION CONTROL WITH GIT 51
Figure 1.26: Code for adding an existing repository.
Click on the HTTPS option,
21
and then copy the commands in the section for
an existing repository. I suggest clicking the small icon on the right side of the
screen, which automatically copies the commands shown in Listing 1.16 to your
pasteboard buffer, allowing you to paste them into the command-line terminal.
Finally, run the commands in Listing 1.16. You will have to type your
GitHub password, but you won’t the next time (as long as it’s within the cache
timeout period) due to the configuration in Listing 1.15.
21
The SSH option shown in Figure 1.26 is excellent for more advanced users, so feel free to use it if you’re
comfortable with generating and configuring SSH keys. Among other things, this option allows your system to
cache your password automatically, rendering the setup step in Listing 1.15 unnecessary.
52 CHAPTER 1. FROM ZERO TO DEPLOY
Listing 1.16: Adding GitHub as a remote origin and pushing up the repository.
$ git remote add origin https://github.com/<username>/hello_app.git
$ git push -u origin master
The commands in Listing 1.16 first tell Git that you want to add GitHub as the
origin for your repository, and then push your repository up to the remote origin.
(Don’t worry about what the -u flag does; if you’re curious, do a web search for
git set upstream”.) Of course, you should replace <username> in Listing 1.16
with your actual username. For example, the command I ran looked like this:
$ git remote add origin https://github.com/mhartl/hello_app.git
The result is a page at GitHub for the hello_app repository, with file browsing,
full commit history, and lots of other features (Figure 1.27).
1.3.4 Branch, edit, commit, merge
If you’ve followed the steps in Section 1.3.3, you might notice that GitHub au-
tomatically rendered the repositorys README file, as shown in Figure 1.28.
This le, called README.md, was generated automatically by the command in
Listing 1.4. As indicated by the filename extension .md, it is written in Mark-
down,
22
a human-readable markup language designed to be easy to convert to
HTML—which is exactly what GitHub has done.
This automatic rendering of the README is convenient, but of course it
would be better if we tailored the contents of the file to the project at hand.
In this section, we’ll customize the README by adding some Rails Tutorial–
specific content. In the process, we’ll see a first example of the branch, edit,
commit, merge workflow that I recommend using with Git.
23
22
See Learn Enough Text Editor to Be Dangerous and Learn Enough Git to Be Dangerous for more information
about Markdown.
23
For a convenient way to visualize Git repositories, take a look at Atlassian’s SourceTree app.
1.3. VERSION CONTROL WITH GIT 53
Figure 1.27: A GitHub repository page.
54 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.28: GitHub’s rendering of the default Rails README.
1.3. VERSION CONTROL WITH GIT 55
Branch
Git is incredibly good at making branches, which are effectively copies of a
repository where we can make (possibly experimental) changes without modi-
fying the parent files. In most cases, the parent repository is the master branch,
and we can create a new topic branch by using checkout with the -b flag:
$ git checkout -b modify-README
Switched to a new branch 'modify-README'
$ git branch
master
* modify-README
Here the second command, git branch, just lists all the local branches, and
the asterisk * identifies which branch we’re currently on. Note that git
checkout -b modify-README both creates a new branch and switches to
it, as indicated by the asterisk in front of the modify-README branch.
The full value of branching only becomes clear when working on a project
with multiple developers,
24
but branches are helpful even for a single-developer
tutorial such as this one. In particular, because the master branch is insulated
from any changes we make to the topic branch, even if we really mess things
up we can always abandon the changes by checking out the master branch and
deleting the topic branch. We’ll see how to do this at the end of the section.
By the way, for a change as small as this one I wouldn’t normally bother
with a new branch (opting instead to work directly on the master branch), but
in the present context it’s a prime opportunity to start practicing good habits.
Edit
After creating the topic branch, we’ll edit the README to add custom content,
as shown in Listing 1.17 and Figure 1.29.
24
See, for example, the section on Collaborating in Learn Enough Git to Be Dangerous.
56 CHAPTER 1. FROM ZERO TO DEPLOY
Figure 1.29: Editing the README file.
Listing 1.17: The new README file.
README.md
# Ruby on Rails Tutorial
## "hello, world!"
This is the first application for the
[*Ruby on Rails Tutorial*](https://www.railstutorial.org/)
by [Michael Hartl](https://www.michaelhartl.com/). Hello, world!
1.3. VERSION CONTROL WITH GIT 57
Commit
With the changes made, we can take a look at the status of our branch:
$ git status
On branch modify-README
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
At this point, we could use git add -A as in Section 1.3.1, but git commit
provides the -a flag as a shortcut for the (very common) case of committing all
modifications to existing files:
$ git commit -a -m "Improve the README file"
[modify-README 34bb6a5] Improve the README file
1 file changed, 5 insertions(+), 22 deletions(-)
Be careful about using the -a ag improperly; if you have added any new files
to the project since the last commit, you still have to tell Git about them using
git add -A first.
Note that we write the commit message in the present tense (and, technically
speaking, the imperative mood). Git models commits as a series of patches,
and in this context it makes sense to describe what each commit does, rather
than what it did. Moreover, this usage matches up with the commit messages
generated by Git commands themselves. See Committing to Git from Learn
Enough Git to Be Dangerous for more information.
Merge
Now that we’ve nished making our changes, we’re ready to merge the results
back into our master branch:
58 CHAPTER 1. FROM ZERO TO DEPLOY
$ git checkout master
Switched to branch 'master'
$ git merge modify-README
Updating b981e57..015008c
Fast-forward
README.md | 27 +++++----------------------
1 file changed, 5 insertions(+), 22 deletions(-)
Note that the Git output frequently includes things like 34f06b7, which are
related to Gits internal representation of repositories. Your exact results will
differ in these details, but otherwise should essentially match the output shown
above.
After you’ve merged in the changes, you can tidy up your branches by delet-
ing the topic branch using git branch -d if you’re done with it:
$ git branch -d modify-README
Deleted branch modify-README (was 015008c).
This step is optional, and in fact it’s quite common to leave the topic branch
intact. This way you can switch back and forth between the topic and master
branches, merging in changes every time you reach a natural stopping point.
As mentioned above, it’s also possible to abandon your topic branch
changes, in this case with git branch -D:
# For illustration only; don't do this unless you mess up a branch
$ git checkout -b topic-branch
$ <really mess up the branch>
$ git add -A
$ git commit -a -m "Make major mistake"
$ git checkout master
$ git branch -D topic-branch
Unlike the -d flag, the -D ag will delete the branch even though we haven’t
merged in the changes.
1.3. VERSION CONTROL WITH GIT 59
Figure 1.30: The improved README file at GitHub.
Push
Now that we’ve updated the README, we can push the changes up to GitHub to
see the result. Since we have already done one push (Section 1.3.3), on most
systems we can omit origin master, and simply run git push:
$ git push
As with the default README, GitHub nicely converts the Markdown in our up-
dated README to HTML (Figure 1.30).
60 CHAPTER 1. FROM ZERO TO DEPLOY
1.4 Deploying
Even though this is only the first chapter, we’re already going to deploy our
Rails application to production! As with the version control setup in Sec-
tion 1.3, this step is technically optional, but deploying early and often allows
us to catch any deployment problems early in our development cycle. The
alternative—deploying only after laborious effort sealed away in a develop-
ment environment—often leads to terrible integration headaches when launch
time comes.
25
Deploying Rails applications used to be a pain, but the Rails deployment
ecosystem has matured rapidly in the past few years, and now there are several
great options. These include shared hosts or virtual private servers running
Phusion Passenger (a module for the Apache and Nginx
26
webservers), full-
service deployment companies such as Engine Yard and Rails Machine, and
cloud deployment services such as Engine Yard Cloud and Heroku.
My favorite Rails deployment option is Heroku, which is a hosted platform
built specifically for deploying Rails and other web applications. (As you might
guess, Heroku itself is written in Rails.) Heroku makes deploying Rails applica-
tions ridiculously easy, as long as your source code is under version control with
Git—which is yet another reason to follow the Git setup steps in Section 1.3 if
you haven’t already. In addition, for many purposes, including for this tutorial,
Heroku’s free tier is more than sufficient.
The rest of this section is dedicated to deploying our first application to
Heroku. Some of the ideas are fairly advanced, so don’t worry about under-
standing all the details; what’s important is that by the end of the process we’ll
have deployed our application to the live web.
1.4.1 Heroku setup and deployment
Heroku uses the PostgreSQL database (pronounced “post-gres-cue-ell”, and of-
ten called “Postgres” for short), which means that we need to add the pg gem
25
Though it shouldn’t matter for the example applications in the Rails Tutorial, if you’re worried about acci-
dentally making your app public too soon there are several options; see Section 1.4.2 for one.
26
Pronounced “Engine X”.
1.4. DEPLOYING 61
in the production environment to allow Rails to talk to Postgres:
group :production do
gem 'pg', '1.1.4'
end
Also be sure to incorporate the changes made in Listing 1.6 preventing the
sqlite3 gem from being included in a production environment, since the
SQLite database isn’t supported at Heroku:
27
group :development, :test do
gem 'sqlite3', '1.4.1'
gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
end
The resulting Gemfile appears as in Listing 1.18.
Important note: For all the Gemfiles in this book, you should use the
version numbers listed at gemfiles-6th-ed.railstutorial.org instead of the
ones listed below (although they should be identical if you are reading this
online).
Listing 1.18: A Gemfile with added and rearranged gems.
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '6.0.2.1'
gem 'puma', '3.12.2'
gem 'sass-rails', '5.1.0'
gem 'webpacker', '4.0.7'
gem 'turbolinks', '5.2.0'
gem 'jbuilder', '2.9.1'
gem 'bootsnap', '1.4.5', require: false
group :development, :test do
gem 'sqlite3', '1.4.1'
gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
27
SQLite is widely used as an embedded database—for instance, it’s ubiquitous in mobile phones—and Rails
uses it locally by default because it’s so easy to set up, but it isn’t designed for database-backed web applications.
See Section 3.1 for more information.
62 CHAPTER 1. FROM ZERO TO DEPLOY
end
group :development do
gem 'web-console', '4.0.1'
gem 'listen', '3.1.5'
gem 'spring', '2.1.0'
gem 'spring-watcher-listen', '2.0.1'
end
group :test do
gem 'capybara', '3.28.0'
gem 'selenium-webdriver', '3.142.4'
gem 'webdrivers', '4.1.2'
end
group :production do
gem 'pg', '1.1.4'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
To prepare the system for deployment to production, we run bundle install
with a special flag to prevent the local installation of any production gems
(which in this case consists of the
pg
gem), as shown in Listing 1.19.
Listing 1.19: Bundling without production gems.
$ bundle install --without production
Because the only gem added in Listing 1.18 is restricted to a production envi-
ronment, right now the command in Listing 1.19 doesn’t actually install any ad-
ditional local gems, but it’s needed to update Gemfile.lock with the pg gem.
We can commit the resulting change as follows:
$ git commit -a -m "Update Gemfile for Heroku"
Next we have to create and configure a new Heroku account. The first step is
to sign up for Heroku. Then check to see if your system already has the Heroku
command-line client installed:
1.4. DEPLOYING 63
$ heroku --version
This will display the current version number if the heroku command-line in-
terface (CLI) is available, but on most systems it will be necessary to install the
Heroku CLI by hand.
28
In particular, if you’re working on the cloud IDE, you
can install Heroku using the command shown in Listing 1.20.
Listing 1.20: The command to install Heroku on the cloud IDE.
$ source <(curl -sL https://cdn.learnenough.com/heroku_install)
After running the command in Listing 1.20, you should now be able to verify
the installation by displaying the current version number (details may vary):
$ heroku --version
heroku/7.27.1 linux-x64 node-v11.14.0
Once you’ve verified that the Heroku command-line interface is installed,
use the heroku command to log in with the mail address and password you used
when signing up (the --interactive option prevents heroku from trying to
spawn a browser):
$ heroku login --interactive
Finally, use the heroku create command to create a place on the Heroku
servers for the sample app to live (Listing 1.21).
Listing 1.21: Creating a new application at Heroku.
$ heroku create
Creating app... done, � blooming-bayou-75897
https://blooming-bayou-75897.herokuapp.com/ |
https://git.heroku.com/blooming-bayou-75897.git
28
toolbelt.heroku.com
64 CHAPTER 1. FROM ZERO TO DEPLOY
The heroku command creates a new subdomain just for our application, avail-
able for immediate viewing. There’s nothing there yet, though, so let’s get busy
deploying.
Heroku deployment, step 1
The first step is to use Git to push the master branch up to Heroku:
$ git push heroku master
(You may see some warning messages, which you should ignore for now. We’ll
discuss them further in Section 7.5.)
Heroku deployment, step 2
There is no step two! We’re already done. To see your newly deployed ap-
plication, visit the address that you saw when you ran heroku create (i.e.,
Listing 1.21).
29
The result appears in Figure 1.31. The page is identical to Fig-
ure 1.20, but now it’s running in a production environment on the live web.
30
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. By making the same change as in Section 1.2.4, arrange for your produc-
tion app to display “hola, mundo!”.
2. As in Section 1.2.4, arrange for the root route to display the result of the
goodbye action. When deploying, confirm that you can omit master in
the Git push, as in git push heroku.
29
If you’re working on your local machine instead of the cloud IDE, you can use heroku open to open the
site automatically in a web browser.
30
Your results may differ if you completed the exercises in Section 1.2.4.
1.4. DEPLOYING 65
Figure 1.31: The first Rails Tutorial application running on Heroku.
66 CHAPTER 1. FROM ZERO TO DEPLOY
1.4.2 Heroku commands
There are many Heroku commands, and we’ll barely scratch the surface in this
book. Let’s take a minute to show just one of them by renaming the application
as follows:
$ heroku rename rails-tutorial-hello
Don’t use this name yourself; it’s already taken by me! In fact, you probably
shouldn’t bother with this step right now; using the default address supplied by
Heroku is fine. But if you do want to rename your application, you can arrange
for it to be reasonably secure by using a random or obscure subdomain, such as
the following:
hwpcbmze.herokuapp.com
seyjhflo.herokuapp.com
jhyicevg.herokuapp.com
With a random subdomain like this, someone could visit your site only if you
gave them the address.
31
(By the way, as a preview of Ruby’s compact awe-
someness, here’s the code I used to generate the random subdomains:
('a'..'z').to_a.shuffle[0..7].join
We’ll return to this bit of code in Chapter 4.)
32
In addition to supporting subdomains, Heroku also supports custom do-
mains. (In fact, the Ruby on Rails Tutorial site lives at Heroku; if you’re read-
ing this book online, you’re looking at a Heroku-hosted site right now!) See the
Heroku documentation for more information about custom domains and other
Heroku topics.
31
This solution, known as “security through obscurity”, is fine for hobby projects, but for sites that require
greater initial security I recommend using Rails HTTP basic authentication. This is a much more advanced tech-
nique, though, and requires significantly more technical sophistication (Box 1.2) to implement. (Thanks to Alfie
Pates for raising this issue.)
32
As is often the case, this code can be made even more compact using a built-in part of Ruby, in this case
something called sample: ('a'..'z').to_a.sample(8).join. Thanks to alert reader Stefan Pochmann for
pointing this out—I didn’t even know about sample until he told me!
1.5. CONCLUSION 67
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Run heroku help to see a list of Heroku commands. What is the com-
mand to display logs for an app?
2. Use the command identified in the previous exercise to inspect the activ-
ity on your application. What was the most recent event? (This command
is often useful when debugging production apps.)
1.5 Conclusion
We’ve come a long way in this chapter: development environment setup, in-
stallation, version control, and deployment. In the next chapter, we’ll build on
the foundation from Chapter 1 to make a database-backed toy app, which will
give us our first real taste of what Rails can do.
If you’d like to share your progress at this point, feel free to send a tweet or
Facebook status update with something like this:
I’m learning Ruby on Rails with the @railstutorial!
https://www.railstutorial.org/
I also recommend signing up for the Rails Tutorial email list
33
, which will en-
sure that you receive priority updates (and exclusive coupon codes) regarding
the
Ruby on Rails Tutorial
.
1.5.1 What we learned in this chapter
Ruby on Rails is a web development framework written in the Ruby pro-
gramming language.
33
railstutorial.org/email
68 CHAPTER 1. FROM ZERO TO DEPLOY
Installing Rails, generating an application, and editing the resulting files
is easy using a preconfigured cloud environment.
Rails comes with a command-line command called rails that can gen-
erate new applications (rails new) and run local servers (rails ser-
ver).
We added a controller action and modified the root route to create a hello,
world” application.
We protected against data loss while enabling collaboration by placing
our application source code under version control with Git and pushing
the resulting code to a private repository at GitHub.
We deployed our application to a production environment using Heroku.
1.6 Conventions used in this book
The conventions used in this book are mostly self-explanatory. In this section,
we’ll go over some that may not be.
This tutorial makes frequent use of command-line commands. For simplic-
ity, all command line examples use a Unix-style command-line prompt (a dollar
sign), as follows:
$ echo "hello, world"
hello, world
Rails comes with many commands that can be run at the command line. For
example, in Section 1.2.2 we’ll run a local development webserver with the
rails server command:
$ rails server
1.6. CONVENTIONS USED IN THIS BOOK 69
As with the command-line prompt, the Rails Tutorial uses the Unix conven-
tion for directory separators (i.e., a forward slash /). For example, the sample
application production.rb configuration file appears as follows:
config/environments/production.rb
This le path should be understood as being relative to the application’s root
directory, which will vary by system. For example, on the cloud IDE (Sec-
tion 1.1.1) it looks like this:
/home/ubuntu/environment/sample_app/
Thus, the full path to production.rb is
/home/ubuntu/environment/sample_app/config/environments/production.rb
I will typically omit the application path and write just config/environ-
ments/production.rb for short.
The Rails Tutorial often shows output from various programs. Because of
the innumerable small differences between different computer systems, the out-
put you see may not always agree exactly with what is shown in the text, but this
is not cause for concern. In addition, some commands may produce errors de-
pending on your system; rather than attempt the Sisyphean task of documenting
all such errors in this tutorial, I will delegate to the “Google the error message”
algorithm, which among other things is good practice for real-life software de-
velopment (Box 1.2). If you run into any problems while following the tutorial,
I suggest consulting the resources listed at the Rails Tutorial Help page.
34
Because the Rails Tutorial covers testing of Rails applications, it is often
helpful to know if a particular piece of code causes the test suite to fail (indicated
by the color red) or pass (indicated by the color green). For convenience, code
34
https://www.railstutorial.org/help
70 CHAPTER 1. FROM ZERO TO DEPLOY
resulting in a failing test is thus indicated with red, while code resulting in a
passing test is indicated with green.
Finally, for convenience the Ruby on Rails Tutorial adopts two conventions
designed to make the many code samples easier to understand. First, some code
listings include one or more highlighted lines, as seen below:
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true
end
Such highlighted lines typically indicate the most important new code in the
given sample, and often (though not always) represent the difference between
the present code listing and previous listings. Second, for brevity and simplicity
many of the book’s code listings include vertical dots, as follows:
class User < ApplicationRecord
.
.
.
has_secure_password
end
These dots represent omitted code and should not be copied literally.
Chapter 2
A toy app
In this chapter, we’ll develop a toy demo application to show off some of the
power of Rails. The purpose is to get a high-level overview of Ruby on Rails
programming (and web development in general) by rapidly generating an appli-
cation using scaffold generators, which create a large amount of functionality
automatically. As discussed in Box 2.1, the rest of the book will take the oppo-
site approach, developing a full sample application incrementally and explain-
ing each new concept as it arises, but for a quick overview (and some instant
gratification) there is no substitute for scaffolding. The resulting toy app will
allow us to interact with it through its URLs, giving us insight into the struc-
ture of a Rails application, including a first example of the REST architecture
favored by Rails.
As with the forthcoming sample application, the toy app will consist of
users and their associated microposts (thus constituting a minimalist Twitter-
style app). The functionality will be utterly under-developed, and many of the
steps will seem like magic, but worry not: the full sample app will develop a
similar application from the ground up starting in Chapter 3, and I will provide
plentiful forward-references to later material. In the meantime, have patience
and a little faith—the whole point of this tutorial is to take you beyond this su-
perficial, scaffold-driven approach to achieve a deeper understanding of Rails.
71
72 CHAPTER 2. A TOY APP
Box 2.1. Scaffolding: Quicker, easier, more seductive
From the beginning, Rails has benefited from a palpable sense of excitement,
starting with the famous 15-minute weblog video by Rails creator David Heine-
meier Hansson. That video and its successors are a great way to get a taste of
Rails’ power, and I recommend watching them. But be warned: they accomplish
their amazing fteen-minute feat using a feature called scaffolding, which relies
heavily on generated code, magically created by the Rails generate scaffold
command.
When writing a Ruby on Rails tutorial, it is tempting to rely on the scaffold-
ing approach—it’s quicker, easier, more seductive. But the complexity and sheer
amount of code in the scaffolding can be utterly overwhelming to a beginning Rails
developer; you may be able to use it, but you probably won’t understand it. Fol-
lowing the scaffolding approach risks turning you into a virtuoso script generator
with little (and brittle) actual knowledge of Rails.
In the Ruby on Rails Tutorial, we’ll take the (nearly) polar opposite approach:
although this chapter will develop a small toy app using scaffolding, the core of the
Rails Tutorial is the sample app, which we’ll start writing in
Chapter 3. At each
stage of developing the sample application, we will write small, bite-sized pieces
of code—simple enough to understand, yet novel enough to be challenging. The
cumulative effect will be a deeper, more flexible knowledge of Rails, giving you a
good background for writing nearly any type of web application.
2.1 Planning the application
In this section, we’ll outline our plans for the toy application. As in Section 1.2,
we’ll start by generating the application skeleton using the rails new com-
mand with a specific Rails version number:
2.1. PLANNING THE APPLICATION 73
$ cd ~/environment
$ rails _6.0.2.1_ new toy_app
$ cd toy_app/
If you’re using the cloud IDE as recommended in Section 1.1.1, note that this
second app can be created in the same environment as the first. It is not neces-
sary to create a new environment. In order to get the files to appear, you may
need to click the gear icon in the file navigator area and select “Refresh File
Tree”.
Next, we’ll use a text editor to update the Gemfile needed by Bundler with
the contents of Listing 2.1.
Important note: For all the Gemfiles in this book, you should use the
version numbers listed at gemfiles-6th-ed.railstutorial.org instead of the
ones listed below (although they should be identical if you are reading this
online).
Listing 2.1: A Gemfile for the toy app.
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '6.0.2.1'
gem 'puma', '3.12.2'
gem 'sass-rails', '5.1.0'
gem 'webpacker', '4.0.7'
gem 'turbolinks', '5.2.0'
gem 'jbuilder', '2.9.1'
gem 'bootsnap', '1.4.5', require: false
group :development, :test do
gem 'sqlite3', '1.4.1'
gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
gem 'web-console', '4.0.1'
gem 'listen', '3.1.5'
gem 'spring', '2.1.0'
gem 'spring-watcher-listen', '2.0.1'
end
group :test do
74 CHAPTER 2. A TOY APP
gem 'capybara', '3.28.0'
gem 'selenium-webdriver', '3.142.4'
gem 'webdrivers', '4.1.2'
end
group :production do
gem 'pg', '1.1.4'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Note that Listing 2.1 is identical to Listing 1.18.
As in Section 1.4.1, we’ll install the local gems while preventing the instal-
lation of production gems using the --without production option:
$ bundle install --without production
As noted in Section 1.2.1, you may need to run bundle update as well
(Box 1.2).
Finally, we’ll put the toy app under version control with Git:
$ git init
$ git add -A
$ git commit -m "Initialize repository"
You should also create a new repository at GitHub by following the same steps
as in Section 1.3.3 (taking care to make it private as in Figure 2.1), and then
push up to the remote repository:
$ git remote add origin https://github.com/<username>/toy_app.git
$ git push -u origin master
Finally, it’s never too early to deploy, which I suggest doing by following
the same “hello, world!” steps from Section 1.2.4, as shown in Listing 2.2 and
Listing 2.3.
2.1. PLANNING THE APPLICATION 75
Figure 2.1: Creating the toy app repository at GitHub.
76 CHAPTER 2. A TOY APP
Listing 2.2: Adding a hello action to the Application controller.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def hello
render html: "hello, world!"
end
end
Listing 2.3: Setting the root route.
config/routes.rb
Rails.application.routes.draw do
root 'application#hello'
end
Then commit the changes and push up to Heroku, and, at the same time,
GitHub—it’s a good idea to keep the two copies in sync:
$ git commit -am "Add hello"
$ heroku create
$ git push && git push heroku master
Here we’ve used the double ampersand operator && (read “and”) to combine
the pushes to GitHub and Heroku; the second command will execute only if the
first one succeeds.
1
As in Section 1.4, you may see some warning messages, which you should
ignore for now. We’ll deal with them in Section 7.5. Apart from the URL of
the Heroku app, the result should be the same as in Figure 1.31.
2.1.1 A toy model for users
Now we’re ready to start making the app itself. The typical first step when
making a web application is to create a data model, which is a representation
1
The && operator is described in Chapter 4 of Learn Enough Command Line to Be Dangerous.
2.1. PLANNING THE APPLICATION 77
email
string
id
name
string
integer
users
Figure 2.2: The data model for users.
of the structures needed by our application, including the relationships between
them. In our case, the toy app will be a Twitter-style microblog, with only users
and short (micro)posts. Thus, we’ll begin with a model for users of the app in
this section, and then we’ll add a model for
microposts
(Section 2.1.2).
There are as many choices for a user data model as there are different reg-
istration forms on the web; for simplicity, we’ll go with a distinctly minimalist
approach. Users of our toy app will have a unique identifier called id (of type
integer), a publicly viewable name (of type string), and an email address
(also of type string) that will double as a unique username. (Note that there
is no password attribute at this point, which is part of what makes this app a
“toy”. We’ll cover passwords starting in Chapter 6.) A summary of the data
model for users appears in Figure 2.2.
As we’ll see starting in Section 6.1.1, the label users in Figure 2.2 cor-
responds to a table in a database, and the id, name, and email attributes are
columns in that table.
2.1.2 A toy model for microposts
Recall from the introduction that a micropost is simply a short post, essentially
a generic term for the brand-specific “tweet” (with the prefix “micro” motivated
by Twitters original description as a “micro-blog”). The core of the micropost
data model is even simpler than the one for users: a micropost has only an
78 CHAPTER 2. A TOY APP
user_id
integer
id
content
text
integer
microposts
Figure 2.3: The data model for microposts.
id and a content field for the micropost’s text (of type text).
2
There’s an
additional complication, though: we want to associate each micropost with a
particular user. We’ll accomplish this by recording the user_id of the owner
of the post. The results are shown in Figure 2.3.
We’ll see in Section 2.3.3 (and more fully in Chapter 13) how this user_id
attribute allows us to succinctly express the notion that a user potentially has
many associated microposts.
2.2 The Users resource
In this section, we’ll implement the users data model in Section 2.1.1, along
with a web interface to that model. The combination will constitute a Users
resource, which will allow us to think of users as objects that can be created,
read, updated, and deleted through the web via the HTTP protocol. As promised
in the introduction, our Users resource will be created by a scaffold generator
program, which comes standard with each Rails project. I urge you not to look
too closely at the generated code; at this stage, it will only serve to confuse you.
Rails scaffolding is generated by passing the scaffold command to the
2
Because microposts are short by design, the string type might actually be big enough to contain them, but
using text better expresses our intent, while also giving us greater flexibility should we ever wish to relax the
length constraint. Indeed, Twitters change from allowing 140 to 280 characters in English-language tweets is a
perfect example of why such flexibility is important: a string typically allows 255 (2
8
1) characters, which
is big enough for 140-character tweets but not for 280-character ones. Using text allows a unified treatment of
both cases.
2.2. THE USERS RESOURCE 79
rails generate script. The argument of the scaffold command is the sin-
gular version of the resource name (in this case, User), together with optional
parameters for the data model’s attributes:
3
$ rails generate scaffold User name:string email:string
invoke active_record
create db/migrate/<timestamp>_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
invoke resource_route
route resources :users
invoke scaffold_controller
create app/controllers/users_controller.rb
invoke erb
create app/views/users
create app/views/users/index.html.erb
create app/views/users/edit.html.erb
create app/views/users/show.html.erb
create app/views/users/new.html.erb
create app/views/users/_form.html.erb
invoke test_unit
create test/controllers/users_controller_test.rb
create test/system/users_test.rb
invoke helper
create app/helpers/users_helper.rb
invoke test_unit
invoke jbuilder
create app/views/users/index.json.jbuilder
create app/views/users/show.json.jbuilder
create app/views/users/_user.json.jbuilder
invoke assets
invoke scss
create app/assets/stylesheets/users.scss
invoke scss
create app/assets/stylesheets/scaffolds.scss
By including name:string and email:string, we have arranged for the
User model to have the form shown in Figure 2.2. (Note that there is no need
to include a parameter for id; it is created automatically by Rails for use as the
primary key in the database.)
3
The name of the scaffold follows the convention of models, which are singular, rather than resources and
controllers, which are plural. Thus, we have User instead of Users.
80 CHAPTER 2. A TOY APP
To proceed with the toy application, we first need to migrate the database
using rails db:migrate, as shown in Listing 2.4.
Listing 2.4: Migrating the database.
$ rails db:migrate
== CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0027s
== CreateUsers: migrated (0.0036s) =============================
The effect of Listing 2.4 is to update the database with our new users data
model. (We’ll learn more about database migrations starting in Section 6.1.1.)
Having run the migration in Listing 2.4, we can run the local webserver in
a separate tab (Figure 1.15). Users of the cloud IDE should first add the same
configuration as in Section 1.2.2 to allow the toy app to be served (Listing 2.5).
Listing 2.5: Allowing connections to the local web server.
config/environments/development.rb
Rails.application.configure do
.
.
.
# Allow Cloud9 connections.
config.hosts.clear
end
Then run the Rails server as in Section 1.2.2:
$ rails server
Now the toy application should be available on the local server as described in
Section 1.2.2. In particular, if we visit the root URL at / (read “slash”, as noted
in Section 1.2.4), we get the same “hello, world!” page shown in Figure 1.20.
2.2. THE USERS RESOURCE 81
URL Action Purpose
/users index page to list all users
/users/1 show page to show user with id 1
/users/new new page to make a new user
/users/1/edit edit page to edit user with id 1
Table 2.1: The correspondence between pages and URLs for the Users resource.
2.2.1 A user tour
In generating the Users resource scaffolding in Section 2.2, Rails created a large
number of pages for manipulating users. For example, the page for listing all
users is at /users, and the page for making a new user is at /users/new. The rest of
this section is dedicated to taking a whirlwind tour through these user pages. As
we proceed, it may help to refer to Table 2.1, which shows the correspondence
between pages and URLs.
We start with the page to show all the users in our application, called index
and located at /users. As you might expect, initially there are no users at all
(Figure 2.4).
To make a new user, we can click on the New User link in Figure 2.4 to
visit the new page at /users/new, as shown in Figure 2.5. In Chapter 7, this will
become the user signup page.
We can create a user by entering name and email values in the text fields and then
clicking the Create User button. The result is the user show page at /users/1,
as seen in Figure 2.6. (The green welcome message is accomplished using the
flash, which we’ll learn about in Section 7.4.2.) Note that the URL is /users/1;
as you might suspect, the number 1 is simply the users id attribute from Fig-
ure 2.2. In Section 7.1, this page will become the users profile page.
To change a users information, we click the Edit link to visit the edit page
at /users/1/edit (Figure 2.7). By modifying the user information and clicking the
Update User button, we arrange to change the information for the user in the toy
application (Figure 2.8). (As we’ll see in detail starting in Chapter 6, this user
data is stored in a database back-end.) We’ll add user edit/update functionality
to the sample application in Section 10.1.
Now we’ll create a second user by revisiting the new page at /users/new and
82 CHAPTER 2. A TOY APP
Figure 2.4: The initial index page for the Users resource (/users).
2.2. THE USERS RESOURCE 83
Figure 2.5: The new user page (/users/new).
84 CHAPTER 2. A TOY APP
Figure 2.6: The page to show a user (/users/1).
2.2. THE USERS RESOURCE 85
Figure 2.7: The user edit page (/users/1/edit).
86 CHAPTER 2. A TOY APP
Figure 2.8: A user with updated information.
2.2. THE USERS RESOURCE 87
Figure 2.9: The user index page (/users) with a second user.
submitting a second set of user information. The resulting user index is shown
in Figure 2.9. Section 7.1 will develop the user index into a more polished page
for showing all users.
Having shown how to create, show, and edit users, we come nally to de-
stroying them (Figure 2.10). You should verify that clicking on the link in Fig-
ure 2.10 destroys the second user, yielding an index page with only one user. (If
it doesn’t work, be sure that JavaScript is enabled in your browser; Rails uses
JavaScript to issue the request needed to destroy a user.) Section 10.4 adds user
deletion to the sample app, taking care to restrict its use to a special class of
administrative users.
88 CHAPTER 2. A TOY APP
Figure 2.10: Destroying a user.
2.2. THE USERS RESOURCE 89
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. (For readers who know CSS) Create a new user, then use your browsers
HTML inspector to determine the CSS id for the text “User was success-
fully created.” What happens when you refresh your browser?
2. What happens if you try to create a user with a name but no email address?
3. What happens if you try create a user with an invalid email address, like
“@example.com”?
4. Destroy each of the users created in the previous exercises. Does Rails
display a message by default when a user is destroyed?
2.2.2 MVC in action
Now that we’ve completed a quick overview of the Users resource, let’s exam-
ine one particular part of it in the context of the Model-View-Controller (MVC)
pattern introduced in Section 1.2.3. Our strategy will be to describe the results
of a typical browser hit—a visit to the user index page at /users—in terms of
MVC (Figure 2.11).
Here is a summary of the steps shown in Figure 2.11:
1. The browser issues a request for the /users URL.
2. Rails routes /users to the index action in the Users controller.
3. The index action asks the User model to retrieve all users (User.all).
4. The User model pulls all the users from the database.
5. The User model returns the list of users to the controller.
90 CHAPTER 2. A TOY APP
Controller
(users_controller.rb)
Rails
router
Model
(user.rb)
View
(index.html.erb)
index
@users
HTML
HTML
User.all
/users1
2
7
4
3
6
5
Database
8
Figure 2.11: A detailed diagram of MVC in Rails.
2.2. THE USERS RESOURCE 91
6. The controller captures the users in the @users variable, which is passed
to the index view.
7. The view uses embedded Ruby to render the page as HTML.
8. The controller passes the HTML back to the browser.
4
Now let’s take a look at the above steps in more detail. We start with a
request issued from the browser—i.e., the result of typing a URL in the address
bar or clicking on a link (Step 1 in Figure 2.11). This request hits the Rails
router
(Step 2), which dispatches the request to the proper
controller action
based on the URL (and, as we’ll see in Box 3.2, the type of request). The code
to create the mapping of user URLs to controller actions for the Users resource
appears in Listing 2.6. This code effectively sets up the table of URL/action
pairs seen in Table 2.1. (The strange notation :users is a symbol, which we’ll
learn about in Section 4.3.3.)
Listing 2.6: The Rails routes, with a rule for the Users resource.
config/routes.rb
Rails.application.routes.draw do
resources :users
root 'application#hello'
end
While we’re looking at the routes file, let’s take a moment to associate the
root route with the users index, so that “slash” goes to /users. Recall from
Listing 2.3 that we added the root route
root 'application#hello'
so that the root route went to the hello action in the Application controller. In
the present case, we want to use the index action in the Users controller, which
we can arrange using the code shown in Listing 2.7.
4
Some references indicate that the view returns the HTML directly to the browser (via a webserver such as
Apache or Nginx). Regardless of the implementation details, I find it helpful to think of the controller as a central
hub through which all the application’s information flows.
92 CHAPTER 2. A TOY APP
Listing 2.7: Adding a root route for users.
config/routes.rb
Rails.application.routes.draw do
resources :users
root 'users#index'
end
A controller contains a collection of related actions, and the pages from the
tour in Section 2.2.1 correspond to actions in the Users controller. The con-
troller generated by the scaffolding is shown schematically in Listing 2.8. Note
the code class UsersController < ApplicationController, which
is an example of a Ruby class with inheritance. (We’ll discuss inheritance
briefly in Section 2.3.4 and cover both subjects in more detail in Section 4.4.)
Listing 2.8: The Users controller in schematic form.
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def index
.
.
.
end
def show
.
.
.
end
def new
.
.
.
end
def edit
.
.
.
2.2. THE USERS RESOURCE 93
end
def create
.
.
.
end
def update
.
.
.
end
def destroy
.
.
.
end
end
You might notice that there are more actions than there are pages; the index,
show, new, and edit actions all correspond to pages from Section 2.2.1, but
there are additional create, update, and destroy actions as well. These
actions don’t typically render pages (although they can); instead, their main
purpose is to modify information about users in the database.
This full suite of controller actions, summarized in Table 2.2, represents the
implementation of the REST architecture in Rails (Box 2.2), which is based on
the ideas of representational state transfer identified and named by computer
scientist Roy Fielding.
5
Note from Table 2.2 that there is some overlap in the
URLs; for example, both the user show action and the update action corre-
spond to the URL /users/1. The difference between them is the HTTP request
method they respond to. We’ll learn more about HTTP request methods starting
in Section 3.3.
Box 2.2. REpresentational State Transfer (REST)
5
Fielding, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures. Doc-
toral dissertation, University of California, Irvine, 2000.
94 CHAPTER 2. A TOY APP
HTTP request URL Action Purpose
GET /users index page to list all users
GET /users/1 show page to show user with id 1
GET /users/new new page to make a new user
POST /users create create a new user
GET /users/1/edit edit page to edit user with id 1
PATCH /users/1 update update user with id 1
DELETE /users/1 destroy delete user with id 1
Table 2.2: RESTful routes provided by the Users resource in Listing 2.6.
If you read much about Ruby on Rails web development, you’ll see a lot of
references to “REST”, which is an acronym for REpresentational State Transfer.
REST is an architectural style for developing distributed, networked systems and
software applications such as the World Wide Web and web applications. Although
REST theory is rather abstract, in the context of Rails applications REST means
that most application components (such as users and microposts) are modeled as
resources that can be created, read, updated, and deleted—operations that corre-
spond both to the CRUD operations of relational databases and to the four funda-
mental HTTP request methods: POST, GET, PATCH, and DELETE. (We’ll learn
more about HTTP requests in Section 3.3 and especially Box 3.2.)
As a Rails application developer, the RESTful style of development helps you
make choices about which controllers and actions to write: you simply structure the
application using resources that get created, read, updated, and deleted. In the case
of users and microposts, this process is straightforward, since they are naturally
resources in their own right. In Chapter 14, we’ll see an example where REST
principles allow us to model a subtler problem, “following users”, in a natural and
convenient way.
To examine the relationship between the Users controller and the User mo-
del, let’s focus on the index action, shown in Listing 2.9. (Learning how to
read code even when you don’t fully understand it is an important aspect of
technical sophistication (Box 1.2).)
2.2. THE USERS RESOURCE 95
Listing 2.9: The simplified user index action for the toy application.
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def index
@users = User.all
end
.
.
.
end
This index action has the line @users = User.all (Step 3 in Figure 2.11),
which asks the User model to retrieve a list of all the users from the database
(Step 4), and then places them in the variable @users (pronounced “at-users”)
(Step 5).
The User model itself appears in Listing 2.10. Although it is rather plain,
it comes equipped with a large amount of functionality because of inheritance
(Section 2.3.4 and Section 4.4). In particular, by using the Rails library called
Active Record, the code in Listing 2.10 arranges for User.all to return all the
users in the database.
Listing 2.10: The User model for the toy application.
app/models/user.rb
class User < ApplicationRecord
end
Once the @users variable is defined, the controller calls the view (Step 6),
shown in Listing 2.11. Variables that start with the @ sign, called instance vari-
ables, are automatically available in the views; in this case, the index.html.-
erb view in Listing 2.11 iterates through the @users list and outputs a line of
HTML for each one. (Remember, you aren’t supposed to understand this code
right now. It is shown only for purposes of illustration.)
96 CHAPTER 2. A TOY APP
Listing 2.11: The view for the users index.
app/views/users/index.html.erb
<p id="notice"><%= notice %></p>
<h1>Users</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New User', new_user_path %>
The view converts its contents to HTML (Step 7), which is then returned by the
controller to the browser for display (Step 8).
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. By referring to Figure 2.11, write out the analogous steps for visiting the
URL /users/1/edit.
2.3. THE MICROPOSTS RESOURCE 97
2. Find the line in the scaffolding code that retrieves the user from the data-
base in the previous exercise. Hint: It’s in a special location called set_-
user.
3. What is the name of the view file for the user edit page?
2.2.3 Weaknesses of this Users resource
Though good for getting a general overview of Rails, the scaffold Users re-
source suffers from a number of severe weaknesses.
No data validations. Our User model accepts data such as blank names
and invalid email addresses without complaint.
No authentication. We have no notion of logging in or out, and no way
to prevent any user from performing any operation.
No tests. This isn’t technically true—the scaffolding includes rudimen-
tary tests—but the generated tests don’t test for data validation, authen-
tication, or any other custom requirements.
No style or layout. There is no consistent site styling or navigation.
No real understanding. If you understand the scaffold code, you prob-
ably shouldn’t be reading this book.
2.3 The Microposts resource
Having generated and explored the Users resource, we turn now to the asso-
ciated Microposts resource. Throughout this section, I recommend comparing
the elements of the Microposts resource with the analogous user elements from
Section 2.2; you should see that the two resources parallel each other in many
ways. The RESTful structure of Rails applications is best absorbed by this sort
of repetition of form—indeed, seeing the parallel structure of Users and Micro-
posts even at this early stage is one of the prime motivations for this chapter.
98 CHAPTER 2. A TOY APP
2.3.1 A micropost microtour
As with the Users resource, we’ll generate scaffold code for the Microposts
resource using rails generate scaffold, in this case implementing the
data model from Figure 2.3:
6
$ rails generate scaffold Micropost content:text user_id:integer
invoke active_record
create db/migrate/<timestamp>_create_microposts.rb
create app/models/micropost.rb
invoke test_unit
create test/models/micropost_test.rb
create test/fixtures/microposts.yml
invoke resource_route
route resources :microposts
invoke scaffold_controller
create app/controllers/microposts_controller.rb
invoke erb
create app/views/microposts
create app/views/microposts/index.html.erb
create app/views/microposts/edit.html.erb
create app/views/microposts/show.html.erb
create app/views/microposts/new.html.erb
create app/views/microposts/_form.html.erb
invoke test_unit
create test/controllers/microposts_controller_test.rb
create test/system/microposts_test.rb
invoke helper
create app/helpers/microposts_helper.rb
invoke test_unit
invoke jbuilder
create app/views/microposts/index.json.jbuilder
create app/views/microposts/show.json.jbuilder
create app/views/microposts/_micropost.json.jbuilder
invoke assets
invoke scss
create app/assets/stylesheets/microposts.scss
invoke scss
identical app/assets/stylesheets/scaffolds.scss
To update our database with the new data model, we need to run a migration as
in Section 2.2:
6
As with the User scaffold, the scaffold generator for microposts follows the singular convention of Rails
models; thus, we have generate Micropost.
2.3. THE MICROPOSTS RESOURCE 99
HTTP request URL Action Purpose
GET /microposts index page to list all microposts
GET /microposts/1 show page to show micropost with id 1
GET /microposts/new new page to make a new micropost
POST /microposts create create a new micropost
GET /microposts/1/edit edit page to edit micropost with id 1
PATCH /microposts/1 update update micropost with id 1
DELETE /microposts/1 destroy delete micropost with id 1
Table 2.3: RESTful routes provided by the Microposts resource in Listing 2.12.
$ rails db:migrate
== CreateMicroposts: migrating ===============================================
-- create_table(:microposts)
-> 0.0023s
== CreateMicroposts: migrated (0.0026s) ======================================
Now we are in a position to create microposts in the same way we created
users in Section 2.2.1. As you might guess, the scaffold generator has updated
the Rails routes file with a rule for Microposts resource, as seen in Listing 2.12.
7
As with users, the resources :microposts routing rule maps micropost
URLs to actions in the Microposts controller, as seen in Table 2.3.
Listing 2.12: The Rails routes, with a new rule for Microposts resources.
config/routes.rb
Rails.application.routes.draw do
resources :microposts
resources :users
root 'users#index'
end
The Microposts controller itself appears in schematic form in Listing 2.13.
Note that, apart from having MicropostsController in place of Users-
Controller, Listing 2.13 is identical to the code in Listing 2.8. This is a
reflection of the REST architecture common to both resources.
7
The scaffold code may have extra blank lines compared to Listing 2.12. This is not a cause for concern, as
Ruby ignores such extra space.
100 CHAPTER 2. A TOY APP
Listing 2.13: The Microposts controller in schematic form.
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
.
.
.
def index
.
.
.
end
def show
.
.
.
end
def new
.
.
.
end
def edit
.
.
.
end
def create
.
.
.
end
def update
.
.
.
end
def destroy
.
.
.
end
end
2.3. THE MICROPOSTS RESOURCE 101
Figure 2.12: The micropost index page (/microposts).
To make some actual microposts, we click on New Micropost on the mi-
cropost index page (Figure 2.12) and enter information at the new microposts
page, /microposts/new, as seen in Figure 2.13.
At this point, go ahead and create a micropost or two, taking care to make
sure that at least one has a user_id of 1 to match the id of the first user created
in Section 2.2.1. The result should look something like Figure 2.14.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
102 CHAPTER 2. A TOY APP
Figure 2.13: The new micropost page (/microposts/new).
2.3. THE MICROPOSTS RESOURCE 103
Figure 2.14: The micropost index page with a couple of posts.
104 CHAPTER 2. A TOY APP
Tutorial course or to the Learn Enough All Access Bundle.
1. (For readers who know CSS) Create a new micropost, then use your
browsers HTML inspector to determine the CSS id for the text “Mi-
cropost was successfully created.” What happens when you refresh your
browser?
2. Try to create a micropost with empty content and no user id.
3. Try to create a micropost with over 140 characters of content (say, the
first paragraph from the Wikipedia article on Ruby).
4. Destroy the microposts from the previous exercises.
2.3.2 Putting the micro in microposts
Any micropost worthy of the name should have some means of enforcing the
length of the post. Implementing this constraint in Rails is easy with valida-
tions; to accept microposts with at most 140 characters la the original design
of Twitter), we use a length validation. At this point, you should open the file
app/models/micropost.rb in your text editor or IDE and fill it with the
contents of Listing 2.14.
Listing 2.14: Constraining microposts to be at most 140 characters.
app/models/micropost.rb
class Micropost < ApplicationRecord
validates :content, length: { maximum: 140 }
end
The code in Listing 2.14 may look rather mysterious—we’ll cover valida-
tions more thoroughly starting in Section 6.2but its effects are readily appar-
ent if we go to the new micropost page and enter more than 140 characters for
the content of the post. As seen in Figure 2.15, Rails renders error messages in-
dicating that the micropost’s content is too long. (We’ll learn more about error
messages in Section 7.3.3.)
2.3. THE MICROPOSTS RESOURCE 105
Figure 2.15: Error messages for a failed micropost creation.
106 CHAPTER 2. A TOY APP
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Try to create a micropost with the same long content used in a previous
exercise (Section 2.3.1). How has the behavior changed?
2. (For readers who know CSS) Use your browsers HTML inspector to
determine the CSS id of the error message produced by the previous ex-
ercise.
2.3.3 A user has_many microposts
One of the most powerful features of Rails is the ability to form associations
between different data models. In the case of our User model, each user poten-
tially has many microposts. We can express this in code by updating the User
and Micropost models as in Listing 2.15 and Listing 2.16.
Listing 2.15: A user has many microposts.
app/models/user.rb
class User < ApplicationRecord
has_many :microposts
end
Listing 2.16: A micropost belongs to a user.
app/models/micropost.rb
class Micropost < ApplicationRecord
belongs_to :user
validates :content, length: { maximum: 140 }
end
2.3. THE MICROPOSTS RESOURCE 107
foo@bar.com
email
mhartl@example.com
2 Foo Bar
id
1 Michael Hartl
name
users
2Another post3
1
user_id
1
2 Second post
id
1 First post!
content
microposts
Figure 2.16: The association between microposts and users.
We can visualize the result of this association in Figure 2.16. Because of
the user_id column in the microposts table, Rails (using Active Record)
can infer the microposts associated with each user.
In Chapter 13 and Chapter 14, we will use the association of users and mi-
croposts both to display all of a users microposts and to construct a Twitter-
like micropost feed. For now, we can examine the implications of the user–
micropost association by using the console, which is a useful tool for interact-
ing with Rails applications. We first invoke the console with rails console
at the command line, and then retrieve the first user from the database using
User.first (putting the results in the variable first_user), as shown in
Listing 2.17.
8
(I include exit in the last line just to demonstrate how to exit
the console. On most systems, you can also use Ctrl-D for the same purpose.)
9
Listing 2.17: Investigating the state of the application using the Rails console.
$ rails console
>> first_user = User.first
(0.5ms) SELECT sqlite_version(*)
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC
LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.org",
created_at: "2019-08-20 00:39:14", updated_at: "2019-08-20 00:41:24">
>> first_user.microposts
Micropost Load (3.2ms) SELECT "microposts".* FROM "microposts" WHERE
"microposts"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 1, content:
8
Your console prompt might be something like 2.6.3 :001 >, but the examples use >> since Ruby versions
will vary.
9
As in the case of Ctrl-C, the capital “D refers to the key on the keyboard, not the capital letter, so you don’t
have to hold down the Shift key along with the Ctrl key.
108 CHAPTER 2. A TOY APP
"First micropost!", user_id: 1, created_at: "2019-08-20 02:04:13", updated_at:
"2019-08-20 02:04:13">, #<Micropost id: 2, content: "Second micropost",
user_id: 1, created_at: "2019-08-20 02:04:30", updated_at: "2019-08-20
02:04:30">]>
>> micropost = first_user.microposts.first
Micropost Load (0.2ms) SELECT "microposts".* FROM "microposts" WHERE
"microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?
[["user_id", 1], ["LIMIT", 1]]
=> #<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:
"2019-08-20 02:04:13", updated_at: "2019-08-20 02:04:13">
>> micropost.user
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.org",
created_at: "2019-08-20 00:39:14", updated_at: "2019-08-20 00:41:24"
>> exit
There’s a lot going on in Listing 2.17, and teasing out the relevant parts is a good
exercise in technical sophistication (Box 1.2). The output includes the actual
return values, which are raw Ruby objects, as well as the Structured Query
Language (SQL) code that produced them.
In addition to retrieving the first user with User.first, Listing 2.17 shows
two other things: (1) how to access the first users microposts using the code
first_user.microposts, which automatically returns all the microposts
with user_id equal to the id of first_user (in this case, 1); and (2) how
to return the user corresponding to a particular post using micropost.user.
We’ll learn much more about the Ruby involved in Listing 2.17 in Chapter 4,
and more about the association facilities in Active Record in Chapter 13 and
Chapter 14.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Edit the user show page to display the content of the users first micropost.
(Use your technical sophistication (Box 1.2) to guess the syntax based on
the other content in the le.) Confirm by visiting /users/1 that it worked.
2.3. THE MICROPOSTS RESOURCE 109
2. The code in Listing 2.18 shows how to add a validation for the presence
of micropost content in order to ensure that microposts can’t be blank.
Verify that you get the behavior shown in Figure 2.17.
3. Update Listing 2.19 by replacing FILL_IN with the appropriate code to
validate the presence of name and email attributes in the User model (Fig-
ure 2.18).
Listing 2.18: Code to validate the presence of micropost content.
app/models/micropost.rb
class Micropost < ApplicationRecord
belongs_to :user
validates :content, length: { maximum: 140 },
presence: true
end
Listing 2.19: Adding presence validations to the User model.
app/models/user.rb
class User < ApplicationRecord
has_many :microposts
validates FILL_IN, presence: true # Replace FILL_IN with the right code.
validates FILL_IN, presence: true # Replace FILL_IN with the right code.
end
2.3.4 Inheritance hierarchies
We end our discussion of the toy application with a brief description of the
controller and model class hierarchies in Rails. This discussion will only make
much sense if you have some experience with object-oriented programming
(OOP), particularly classes. Don’t worry if it’s confusing for now; we’ll discuss
these ideas more thoroughly in Section 4.4.
We start with the inheritance structure for models. Comparing Listing 2.20
and Listing 2.21, we see that both the User model and the Micropost model in-
herit (via the left angle bracket <) from ApplicationRecord, which in turn
110 CHAPTER 2. A TOY APP
Figure 2.17: The effect of a micropost presence validation.
2.3. THE MICROPOSTS RESOURCE 111
Figure 2.18: The effect of presence validations on the User model.
112 CHAPTER 2. A TOY APP
inherits from ActiveRecord::Base, which is the base class for models pro-
vided by Active Record; a diagram summarizing this relationship appears in
Figure 2.19. It is by inheriting from ActiveRecord::Base that our model
objects gain the ability to communicate with the database, treat the database
columns as Ruby attributes, and so on.
Listing 2.20: The User class, highlighting inheritance.
app/models/user.rb
class User < ApplicationRecord
.
.
.
end
Listing 2.21: The Micropost class, highlighting inheritance.
app/models/micropost.rb
class Micropost < ApplicationRecord
.
.
.
end
The inheritance structure for controllers is essentially the same as that for
models. Comparing Listing 2.22 and Listing 2.23, we see that both the Users
controller and the Microposts controller inherit from the Application controller.
Examining Listing 2.24, we see that ApplicationController itself inherits
from ActionController::Base, which is the base class for controllers pro-
vided by the Rails library Action Pack. The relationships between these classes
is illustrated in Figure 2.20.
Listing 2.22: The UsersController class, highlighting inheritance.
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
end
2.3. THE MICROPOSTS RESOURCE 113
ActiveRecord::Base
User Micropost
ApplicationRecord
Figure 2.19: The inheritance hierarchy for the User and Micropost models.
Listing 2.23: The MicropostsController class, highlighting inheritance.
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
.
.
.
end
Listing 2.24: The ApplicationController class, highlighting inheri-
tance.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
.
.
.
end
As with model inheritance, both the Users and Microposts controllers gain
a large amount of functionality by inheriting from a base class (in this case,
ActionController::Base), including the ability to manipulate model ob-
114 CHAPTER 2. A TOY APP
ActionController::Base
UsersController MicropostsController
ApplicationController
Figure 2.20: The inheritance hierarchy for the Users and Microposts controllers.
jects, filter inbound HTTP requests, and render views as HTML. Since all Rails
controllers inherit from ApplicationController, rules defined in the Ap-
plication controller automatically apply to every action in the application. For
example, in Section 9.1 we’ll see how to include helpers for logging in and
logging out of all of the sample application’s controllers.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. By examining the contents of the Application controller file, nd the
line that causes ApplicationController to inherit from Action-
Controller::Base.
2. Is there an analogous file containing a line where ApplicationRecord
inherits from ActiveRecord::Base? Hint: It would probably be a file
called something like application_record.rb in the app/models
directory.
2.3. THE MICROPOSTS RESOURCE 115
2.3.5 Deploying the toy app
With the completion of the Microposts resource, now is a good time to push the
repository up to GitHub:
$ git status # It's a good habit to check the status before adding
$ git add -A
$ git commit -m "Finish toy app"
$ git push
Ordinarily, you should make smaller, more frequent commits, but for the pur-
poses of this chapter a single big commit at the end is fine.
At this point, you can also deploy the toy app to Heroku as in Section 1.4:
$ git push heroku
(This assumes you created the Heroku app in Section 2.1. Otherwise, you
should run heroku create and then git push heroku master.)
At this point, visiting the page at Heroku yields an error message, as shown
in Figure 2.21.
We can track down the problem by inspecting the Heroku logs:
$ heroku logs
Scrolling up in the logs, you should see a line that includes something like this:
ActionView::Template::Error (PG::UndefinedTable: ERROR: relation "users" does
not exist
This a big hint that there is a missing users table. Luckily, we learned how
to handle that way back in Listing 2.4: all we need to do is run the database
migrations (which will create the microposts table as well).
The way to execute this sort of command at Heroku is to prefix the usual
Rails command with heroku run, like this:
116 CHAPTER 2. A TOY APP
Figure 2.21: An error page at Heroku.
2.4. CONCLUSION 117
$ heroku run rails db:migrate
This updates the database at Heroku with the user and micropost data models
as required. After running the migration, you should be able to use the toy app
in production, with a real PostgreSQL database back-end (Figure 2.22).
10
Finally, if you completed the exercises in Section 2.3.3, you will have to
remove the code to display the first users micropost in order to get the app
to load properly. In this case, simply delete the offending code, make another
commit, and push again to Heroku.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Create a few users on the production app.
2. Create a few production microposts for the first user.
3. By trying to create a micropost with content over 140 characters, confirm
that the validation from Listing 2.14 works on the production app.
2.4 Conclusion
We’ve come now to the end of the high-level overview of a Rails application.
The toy app developed in this chapter has several strengths and a host of weak-
nesses.
Strengths
High-level overview of Rails
10
The production database should work without any additional configuration, but in fact some configuration is
recommended by the official Heroku documentation. We’ll take care of this detail in Section 7.5.3.
118 CHAPTER 2. A TOY APP
Figure 2.22: Running the toy app in production.
2.4. CONCLUSION 119
Introduction to MVC
First taste of the REST architecture
Beginning data modeling
A live, database-backed web application in production
Weaknesses
No custom layout or styling
No static pages (such as “Home or “About”)
No user passwords
No user images
No logging in
No security
No automatic user/micropost association
No notion of “following or “followed”
No micropost feed
No meaningful tests
No real understanding
The rest of this tutorial is dedicated to building on the strengths and eliminating
the weaknesses.
120 CHAPTER 2. A TOY APP
2.4.1 What we learned in this chapter
Scaffolding automatically creates code to model data and interact with it
through the web.
Scaffolding is good for getting started quickly but is bad for understand-
ing.
Rails uses the Model-View-Controller (MVC) pattern for structuring web
applications.
As interpreted by Rails, the REST architecture includes a standard set of
URLs and controller actions for interacting with data models.
Rails supports data validations to place constraints on the values of data
model attributes.
Rails comes with built-in functions for defining associations between dif-
ferent data models.
We can interact with Rails applications at the command line using the
Rails console.
Chapter 3
Mostly static pages
In this chapter, we will begin developing the professional-grade sample applica-
tion that will serve as our example throughout the rest of this tutorial. Although
the sample app will eventually have users, microposts, and a full login and au-
thentication framework, we will begin with a seemingly limited topic: the cre-
ation of static pages. Despite its apparent simplicity, making static pages is a
highly instructive exercise, rich in implications—a perfect start for our nascent
application.
Although Rails is designed for making database-backed dynamic websites,
it also excels at making the kind of static pages we might create using raw
HTML files. In fact, using Rails even for static pages yields a distinct advan-
tage: we can easily add just a small amount of dynamic content. In this chapter
we’ll learn how. Along the way, we’ll get our first taste of automated testing,
which will help us be more confident that our code is correct. Moreover, having
a good test suite will allow us to refactor our code with confidence, changing
its form without changing its function.
121
122 CHAPTER 3. MOSTLY STATIC PAGES
3.1 Sample app setup
As in Chapter 2, before getting started we need to create a new Rails project,
this time called sample_app, as shown in Listing 3.1.
1
Listing 3.1: Generating a new sample app.
$ cd ~/environment
$ rails _6.0.2.1_ new sample_app
$ cd sample_app/
(As in Section 2.1, note that users of the cloud IDE can create this project in the
same environment as the applications from the previous two chapters. It is not
necessary to create a new environment.)
Note: For convenience, a reference implementation of the sample app is
available at GitHub,
2
with a separate branch for each chapter in the tutorial.
As in Section 2.1, our next step is to use a text editor to update the Gemfile
with the gems needed by our application. Listing 3.2 is identical to Listing 1.6
and Listing 2.1 apart from the gems in the test group, which are needed for
the optional advanced testing setup (Section 3.6) and integration testing starting
in Section 5.3.4. Note: If you would like to install all the gems needed for the
sample application, you should use the code in Listing 13.75 at this time.
Important note: For all the Gemfiles in this book, you should use the
version numbers listed at gemfiles-6th-ed.railstutorial.org instead of the
ones listed below (although they should be identical if you are reading this
online).
1
If you’re using the cloud IDE, its often useful to use the “Go to Anything” command (under the “Go” menu),
which makes it easy to navigate the filesystem by typing in partial filenames. In this context, having the hello,
toy, and sample apps present in the same project can be inconvenient due to the many common filenames. For
example, when searching for a file called “Gemfile”, six possibilities will show up, because each project has
matching files called Gemfile and Gemfile.lock. Thus, you may want to consider removing the first two
apps before proceeding, which you can do by navigating to the environment directory and running rm -rf
hello_app/ toy_app/ (Table 1.1). (As long as you pushed the corresponding repositories up to GitHub, you
can always recover them later.)
2
https://github.com/mhartl/sample_app_6th_ed
3.1. SAMPLE APP SETUP 123
Listing 3.2: A Gemfile for the sample app.
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '6.0.2.1'
gem 'puma', '3.12.2'
gem 'sass-rails', '5.1.0'
gem 'webpacker', '4.0.7'
gem 'turbolinks', '5.2.0'
gem 'jbuilder', '2.9.1'
gem 'bootsnap', '1.4.5', require: false
group :development, :test do
gem 'sqlite3', '1.4.1'
gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
gem 'web-console', '4.0.1'
gem 'listen', '3.1.5'
gem 'spring', '2.1.0'
gem 'spring-watcher-listen', '2.0.1'
end
group :test do
gem 'capybara', '3.28.0'
gem 'selenium-webdriver', '3.142.4'
gem 'webdrivers', '4.1.2'
gem 'rails-controller-testing', '1.0.4'
gem 'minitest', '5.11.3'
gem 'minitest-reporters', '1.3.8'
gem 'guard', '2.15.0'
gem 'guard-minitest', '2.4.6'
end
group :production do
gem 'pg', '1.1.4'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
As in the previous two chapters, we run bundle install to install and
include the gems specified in the Gemfile, while skipping the installation of
production gems using the option --without production:
3
3
It’s worth noting that --without production is a “remembered option”, which means it will be included
124 CHAPTER 3. MOSTLY STATIC PAGES
$ bundle install --without production
This arranges to skip the pg gem for PostgreSQL in development and use
SQLite for development and testing. Heroku recommends against using dif-
ferent databases in development and production, but for the sample application
it won’t make any difference, and SQLite is much easier than PostgreSQL to
install and configure locally.
4
In case you’ve previously installed a version of
a gem (such as Rails itself) other than the one specified by the Gemfile, it’s a
good idea to update the gems with bundle update to make sure the versions
match:
$ bundle update
With that, all we have left is to initialize the Git repository:
$ git init
$ git add -A
$ git commit -m "Initialize repository"
As with the first application, I suggest updating the README file to be
more helpful and descriptive by replacing the default contents of README.md
with the Markdown shown in Listing 3.3. The README includes instructions
for getting started with the application.
5
(We won’t actually need to run rails
db:migrate until Chapter 6, but it does no harm to include it now.)
Note: For convenience, the full reference app README contains additional
advanced information not present in Listing 3.3.
automatically the next time we run bundle install.
4
Generally speaking, it’s a good idea for the development and production environments to match as closely as
possible, which includes using the same database, so I recommend eventually learning how to install and configure
PostgreSQL in development—but now is not that time. When the time comes, Google “install configure postgresql
<your system>” and “rails postgresql setup”, and prepare for a challenge. (On the cloud IDE, <your system> is
Linux.)
5
The README also makes reference to a LICENSE file, which I’ve added by hand to the official reference
implementation, but it isn’t present by default. You can download a copy from the reference implementation repo
if you want it for completeness, but it’s not necessary for completing the tutorial.
3.1. SAMPLE APP SETUP 125
Listing 3.3: An improved README file for the sample app.
README.md
# Ruby on Rails Tutorial sample application
This is the sample application for
[*Ruby on Rails Tutorial:
Learn Web Development with Rails*](https://www.railstutorial.org/)
(6th Edition)
by [Michael Hartl](https://www.michaelhartl.com/).
## License
All source code in the [Ruby on Rails Tutorial](https://www.railstutorial.org/)
is available jointly under the MIT License and the Beerware License. See
[LICENSE.md](LICENSE.md) for details.
## Getting started
To get started with the app, clone the repo and then install the needed gems:
```
$ bundle install --without production
```
Next, migrate the database:
```
$ rails db:migrate
```
Finally, run the test suite to verify that everything is working correctly:
```
$ rails test
```
If the test suite passes, you'll be ready to run the app in a local server:
```
$ rails server
```
For more information, see the
[*Ruby on Rails Tutorial* book](https://www.railstutorial.org/book).
Then commit the changes as follows:
126 CHAPTER 3. MOSTLY STATIC PAGES
$ git commit -am "Improve the README"
You may recall from Section 1.3.4 that we used the Git command git commit
-a -m "Message", with flags for “all changes” (-a) and a message (-m). As
shown in the second command above, Git also lets us roll the two flags into one
using git commit -am "Message".
You should also create a new repository at GitHub by following the same
steps as in Section 1.3.3 (taking care to make it private as in Figure 3.1), and
then push up to the remote repository:
$ git remote add origin https://github.com/<username>/sample_app.git
$ git push -u origin master
If you’re using the cloud IDE, you’ll need to prepare the application to be
served locally by editing the development.rb file as in the previous two chap-
ters (Listing 3.4).
Listing 3.4: Allowing connections to the local web server.
config/environments/development.rb
Rails.application.configure do
.
.
.
# Allow connections to local server.
config.hosts.clear
end
To avoid integration headaches later on, it’s also a good idea to deploy the
app to Heroku even at this early stage. As in Chapter 1 and Chapter 2, I suggest
following the “hello, world!” steps in Listing 3.5 and Listing 3.6. (The main
reason for this is that the default Rails page typically breaks at Heroku, which
makes it hard to tell if the deployment was successful or not.)
3.1. SAMPLE APP SETUP 127
Figure 3.1: Creating the main sample app repository at GitHub.
128 CHAPTER 3. MOSTLY STATIC PAGES
Listing 3.5: Adding a hello action to the Application controller.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def hello
render html: "hello, world!"
end
end
Listing 3.6: Setting the root route.
config/routes.rb
Rails.application.routes.draw do
root 'application#hello'
end
Then commit the changes and push up to GitHub and Heroku:
$ git commit -am "Add hello"
$ git push
$ heroku create
$ git push heroku master
As in Section 1.4, you may see some warning messages, which you should
ignore for now. We’ll deal with them in Section 7.5. Apart from the address of
the Heroku app, the result should be the same as in Figure 1.31.
As you proceed through the rest of the book, I recommend pushing and
deploying the application regularly, which automatically makes remote backups
and lets you catch any production errors as soon as possible. If you run into
problems at Heroku, make sure to take a look at the production logs to try to
diagnose the problem:
$ heroku logs # to see the most recent events
$ heroku logs --tail # to see events as they happen, Ctrl-C to quit
Note: If you do end up using Heroku for a real-life application, be sure to follow
the production webserver configuration in Section 7.5.
3.2. STATIC PAGES 129
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Confirm that GitHub renders the Markdown for the README in List-
ing 3.3 as HTML (Figure 3.2).
2. By visiting the root route on the production server, verify that the deploy-
ment to Heroku succeeded.
3.2 Static pages
With all the preparation from Section 3.1 nished, we’re ready to get started
developing the sample application. In this section, we’ll take a first step toward
making dynamic pages by creating a set of Rails
actions
and
views
contain-
ing only static HTML.
6
Rails actions come bundled together inside controllers
(the C in MVC from Section 1.2.3), which contain sets of actions related by a
common purpose. We got a glimpse of controllers in Chapter 2, and will come
to a deeper understanding once we explore the REST architecture more fully
(starting in Chapter 6). In order to get our bearings, it’s helpful to recall the
Rails directory structure from Section 1.2 (Figure 1.11). In this section, we’ll
be working mainly in the app/controllers and app/views directories.
Recall from Section 1.3.4 that, when using Git, it’s a good practice to do our
work on a separate topic branch rather than the master branch. If you’re using
Git for version control, you should run the following command to check out a
topic branch for static pages:
6
Our method for making static pages is probably the simplest, but it’s not the only way. The optimal method
really depends on your needs; if you expect a large number of static pages, using a Static Pages controller can get
quite cumbersome, but in our sample app we’ll only need a few. If you do need a lot of static pages, take a look at
the high_voltage gem from thoughtbot.
130 CHAPTER 3. MOSTLY STATIC PAGES
Figure 3.2: The sample app README at GitHub.
3.2. STATIC PAGES 131
$ git checkout -b static-pages
3.2.1 Generated static pages
To get started with static pages, we’ll first generate a controller using the same
Rails generate script we used in Chapter 2 to generate scaffolding. Since
we’ll be making a controller to handle static pages, we’ll call it the Static Pages
controller, designated by the CamelCase name StaticPages. We’ll also plan
to make actions for a Home page, a Help page, and an About page, designated
by the lower-case action names home, help, and about. The generate script
takes an optional list of actions, so we’ll include actions for the Home and Help
pages directly on the command line, while intentionally leaving off the action
for the About page so that we can see how to add it (Section 3.3). The resulting
command to generate the Static Pages controller appears in Listing 3.7.
Listing 3.7: Generating a Static Pages controller.
$ rails generate controller StaticPages home help
create app/controllers/static_pages_controller.rb
route get 'static_pages/home'
get 'static_pages/help'
invoke erb
create app/views/static_pages
create app/views/static_pages/home.html.erb
create app/views/static_pages/help.html.erb
invoke test_unit
create test/controllers/static_pages_controller_test.rb
invoke helper
create app/helpers/static_pages_helper.rb
invoke test_unit
invoke assets
invoke scss
create app/assets/stylesheets/static_pages.scss
Incidentally, it’s worth noting that rails g is a shortcut for rails gener-
ate, which is only one of several shortcuts supported by Rails (Table 3.1). For
clarity, this tutorial always uses the full command, but in real life most Rails
132 CHAPTER 3. MOSTLY STATIC PAGES
Full command Shortcut
$ rails server $ rails s
$ rails console $ rails c
$ rails generate $ rails g
$ rails test $ rails t
$ bundle install $ bundle
Table 3.1: Some Rails shortcuts.
developers use one or more of the shortcuts shown in Table 3.1.
7
Before moving on, if you’re using Git it’s a good idea to add the files for
the Static Pages controller to the remote repository:
$ git add -A
$ git commit -m "Add a Static Pages controller"
$ git push -u origin static-pages
The final command here arranges to push the static-pages topic branch up
to GitHub. Subsequent pushes can omit the other arguments and write simply
$ git push
The commit and push sequence above represents the kind of pattern I would
ordinarily follow in real-life development, but for simplicity I’ll typically omit
such intermediate commits from now on. (When following this tutorial, a good
rule of thumb is to make a Git commit at the end of each section.)
In Listing 3.7, note that we have passed the controller name as CamelCase
(so called because it resembles the humps of a Bactrian camel), which leads
to the creation of a controller file written in snake case, so that a controller
called StaticPages yields a le called static_pages_controller.rb. This
is merely a convention, and in fact using snake case at the command line also
works: the command
7
In fact, many Rails developers also add an alias (as described in Learn Enough Text Editor to Be Dangerous)
for the rails command, typically shortening it to just r. This allows us to run, e.g., a Rails server using the
compact command r s.
3.2. STATIC PAGES 133
$ rails generate controller static_pages ...
also generates a controller called static_pages_controller.rb. Because
Ruby uses CamelCase for class names (Section 4.4), my preference is to refer
to controllers using their CamelCase names, but this is a matter of taste. (Since
Ruby filenames typically use snake case, the Rails generator converts Camel-
Case to snake case using the underscore method.)
By the way, if you ever make a mistake when generating code, it’s useful to
know how to reverse the process. See Box 3.1 for some techniques on how to
undo things in Rails.
Box 3.1. Undoing things
Even when you’re very careful, things can sometimes go wrong when devel-
oping Rails applications. Happily, Rails has some facilities to help you recover.
One common scenario is wanting to undo code generation—for example, when
you change your mind on the name of a controller and want to eliminate the gener-
ated files. Because Rails creates a substantial number of auxiliary files along with
the controller (as seen in Listing 3.7), this isn’t as easy as removing the controller
file itself; undoing the generation means removing not only the principal generated
file, but all the ancillary les as well. (In fact, as we saw in Section 2.2 and Sec-
tion 2.3, rails generate can make automatic edits to the routes.rb le,
which we also want to undo automatically.) In Rails, this can be accomplished with
rails destroy followed by the name of the generated element. In particular,
these two commands cancel each other out:
$ rails generate controller StaticPages home help
$ rails destroy controller StaticPages home help
Similarly, in Chapter 6 we’ll generate a model as follows:
$ rails generate model User name:string email:string
134 CHAPTER 3. MOSTLY STATIC PAGES
This can be undone using
$ rails destroy model User
(In this case, it turns out we can omit the other command-line arguments. When
you get to Chapter 6, see if you can figure out why.)
Another technique related to models involves undoing migrations, which we
saw briefly in Chapter 2 and will see much more of starting in Chapter 6. Migra-
tions change the state of the database using the command
$ rails db:migrate
We can undo a single migration step using
$ rails db:rollback
To go all the way back to the beginning, we can use
$ rails db:migrate VERSION=0
As you might guess, substituting any other number for 0 migrates to that version
number, where the version numbers come from listing the migrations sequentially.
With these techniques in hand, we are well-equipped to recover from the in-
evitable development snafus.
The Static Pages controller generation in Listing 3.7 automatically updates
the routes file (config/routes.rb), which we first saw in Section 1.2.4 when
we edited the root route for the hello app (Listing 1.11), and which we most
recently saw in Listing 3.6. The routes file is responsible for implementing
the router (seen in Figure 2.11) that defines the correspondence between URLs
and web pages. The routes file is located in the config directory, where Rails
collects files needed for the application configuration (Figure 3.3).
Since we included the home and help actions in Listing 3.7, the routes file
3.2. STATIC PAGES 135
Figure 3.3: Contents of the sample app’s config directory.
136 CHAPTER 3. MOSTLY STATIC PAGES
already has a rule for each one, as seen in Listing 3.8.
Listing 3.8: The routes for the home and help actions in the Static Pages
controller.
config/routes.rb
Rails.application.routes.draw do
get 'static_pages/home'
get 'static_pages/help'
root 'application#hello'
end
Here the rule
get 'static_pages/home'
maps requests for the URL /static_pages/home to the home action in the Static
Pages controller. Moreover, by using get we arrange for the route to respond
to a GET request, which is one of the fundamental HTTP verbs supported by
the Hypertext Transfer Protocol (Box 3.2). In our case, this means that when
we generate a home action inside the Static Pages controller we automatically
get a page at the address /static_pages/home. To see the result, start a Rails
development server as described in Section 1.2.2:
$ rails server
Then navigate to /static_pages/home (Figure 3.4).
Box 3.2. GET, et cet.
The Hypertext Transfer Protocol (HTTP) defines the basic operations GET,
POST, PATCH, and DELETE. These refer to operations between a client com-
puter (typically running a web browser such as Chrome, Firefox, or Safari) and a
3.2. STATIC PAGES 137
Figure 3.4: The raw home view (/static_pages/home).
138 CHAPTER 3. MOSTLY STATIC PAGES
server (typically running a webserver such as Apache or Nginx). (It’s important
to understand that, when developing Rails applications on a local computer, the
client and server are the same physical machine, but in general they are different.)
An emphasis on HTTP verbs is typical of web frameworks (including Rails) influ-
enced by the REST architecture, which we saw briefly in Chapter 2 and will start
learning more about in Chapter 7.
GET is the most common HTTP operation, used for reading data on the web;
it just means “get a page”, and every time you visit a site like https://www.
google.com/ or https://www.wikipedia.org/ your browser is sub-
mitting a GET request. POST is the next most common operation; it is the request
sent by your browser when you submit a form. In Rails applications, POST re-
quests are typically used for creating things (although HTTP also allows POST to
perform updates). For example, the POST request sent when you submit a registra-
tion form creates a new user on the remote site. The other two verbs, PATCH and
DELETE, are designed for updating and destroying things on the remote server.
These requests are less common than GET and POST since browsers are inca-
pable of sending them natively, but some web frameworks (including Ruby on
Rails) have clever ways of making it seem like browsers are issuing such requests.
As a result, Rails supports all four of the request types GET, POST, PATCH, and
DELETE.
To understand where this page comes from, let’s start by taking a look at the
Static Pages controller in a text editor, which should look something like List-
ing 3.9. You may note that, unlike the demo Users and Microposts controllers
from Chapter 2, the Static Pages controller does not use the standard REST ac-
tions. This is normal for a collection of static pages: the REST architecture
isn’t the best solution to every problem.
Listing 3.9: The Static Pages controller made by Listing 3.7.
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
3.2. STATIC PAGES 139
end
def help
end
end
We see from the class keyword in Listing 3.9 that static_pages_con-
troller.rb defines a class, in this case called StaticPagesController.
Classes are simply a convenient way to organize functions (also called meth-
ods) like the home and help actions, which are defined using the def key-
word. As discussed in Section 2.3.4, the angle bracket < indicates that Static-
PagesController inherits from the Rails class ApplicationController;
as we’ll see in a moment, this means that our pages come equipped with a large
amount of Rails-specific functionality. (We’ll learn more about both classes
and inheritance in Section 4.4.)
In the case of the Static Pages controller, both of its methods are initially
empty:
def home
end
def help
end
In plain Ruby, these methods would simply do nothing. In Rails, the situation
is different—StaticPagesController is a Ruby class, but because it in-
herits from ApplicationController the behavior of its methods is specific
to Rails: when visiting the URL /static_pages/home, Rails looks in the Static
Pages controller and executes the code in the home action, and then renders the
view (the V in MVC from Section 1.2.3) corresponding to the action. In the
present case, the home action is empty, so all visiting /static_pages/home does
is render the view. So, what does a view look like, and how do we find it?
If you take another look at the output in Listing 3.7, you might be able to
guess the correspondence between actions and views: an action like home has
a corresponding view called home.html.erb. We’ll learn in Section 3.4 what
the .erb part means; from the .html part you probably won’t be surprised that
it basically looks like HTML (Listing 3.10).
140 CHAPTER 3. MOSTLY STATIC PAGES
Listing 3.10: The generated view for the Home page.
app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>
The view for the help action is analogous (Listing 3.11).
Listing 3.11: The generated view for the Help page.
app/views/static_pages/help.html.erb
<h1>StaticPages#help</h1>
<p>Find me in app/views/static_pages/help.html.erb</p>
Both of these views are just placeholders: they have a top-level heading (inside
the h1 tag) and a paragraph (p tag) with the full path to the corresponding file.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Generate a controller called Foo with actions bar and baz.
2. By applying the techniques described in Box 3.1, destroy the Foo con-
troller and its associated actions.
3.2.2 Custom static pages
We’ll add some (very slightly) dynamic content starting in Section 3.4, but as
they stand the files shown in Listing 3.10 and Listing 3.11 underscore an im-
portant point: Rails views can simply contain static HTML. This means we can
begin customizing the Home and Help pages even with no knowledge of Rails,
as shown in Listing 3.12 and Listing 3.13.
3.3. GETTING STARTED WITH TESTING 141
Listing 3.12: Custom HTML for the Home page.
app/views/static_pages/home.html.erb
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
Listing 3.13: Custom HTML for the Help page.
app/views/static_pages/help.html.erb
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="https://www.railstutorial.org/help">Rails Tutorial Help page</a>.
To get help on this sample app, see the
<a href="https://www.railstutorial.org/book"><em>Ruby on Rails Tutorial</em>
book</a>.
</p>
The results of Listing 3.12 and Listing 3.13 are shown in Figure 3.5 and
Figure 3.6.
3.3 Getting started with testing
Having created and filled in the Home and Help pages for our sample app (Sec-
tion 3.2.2), now we’re going to add an About page as well. When making a
change of this nature, it’s a good practice to write an automated test to verify
that the feature is implemented correctly. Developed over the course of building
an application, the resulting test suite serves as a safety net and as executable
documentation of the application source code. When done right, writing tests
also allows us to develop faster despite requiring extra code, because we’ll end
up wasting less time trying to track down bugs. This is true only once we get
good at writing tests, though, which is one reason it’s important to start practic-
ing as early as possible.
142 CHAPTER 3. MOSTLY STATIC PAGES
Figure 3.5: A custom Home page.
3.3. GETTING STARTED WITH TESTING 143
Figure 3.6: A custom Help page.
144 CHAPTER 3. MOSTLY STATIC PAGES
Although virtually all Rails developers agree that testing is a good idea,
there is a diversity of opinion on the details. There is an especially lively debate
over the use of test-driven development (TDD),
8
a testing technique in which
the programmer writes failing tests rst, and then writes the application code to
get the tests to pass. The Ruby on Rails Tutorial takes a lightweight, intuitive
approach to testing, employing TDD when convenient without being dogmatic
about it (Box 3.3).
Box 3.3. When to test
When deciding when and how to test, it’s helpful to understand why to test. In
my view, writing automated tests has three main benefits:
1. Tests protect against regressions, where a functioning feature stops working
for some reason.
2. Tests allow code to be refactored (i.e., changing its form without changing
its function) with greater confidence.
3. Tests act as a client for the application code, thereby helping determine its
design and its interface with other parts of the system.
Although none of the above benefits require that tests be written first, there are
many circumstances where test-driven development (TDD) is a valuable tool to
have in your kit. Deciding when and how to test depends in part on how comfort-
able you are writing tests; many developers find that, as they get better at writing
tests, they are more inclined to write them first. It also depends on how difficult
the test is relative to the application code, how precisely the desired features are
known, and how likely the feature is to break in the future.
In this context, it’s helpful to have a set of guidelines on when we should test
first (or test at all). Here are some suggestions based on my own experience:
8
See, e.g., TDD is dead. Long live testing. by Rails creator David Heinemeier Hansson.
3.3. GETTING STARTED WITH TESTING 145
When a test is especially short or simple compared to the application code it
tests, lean toward writing the test first.
When the desired behavior isn’t yet crystal clear, lean toward writing the
application code first, then write a test to codify the result.
Because security is a top priority, err on the side of writing tests of the secu-
rity model first.
Whenever a bug is found, write a test to reproduce it and protect against
regressions, then write the application code to fix it.
Lean against writing tests for code (such as detailed HTML structure) likely
to change in the future.
Write tests before refactoring code, focusing on testing error-prone code
that’s especially likely to break.
In practice, the guidelines above mean that we’ll usually write controller and
model tests first and integration tests (which test functionality across models,
views, and controllers) second. And when we’re writing application code that isn’t
particularly brittle or error-prone, or is likely to change (as is often the case with
views), we’ll often skip testing altogether.
Our main testing tools will be controller tests (starting in this section), model
tests (starting in Chapter 6), and integration tests (starting in Chapter 7). Inte-
gration tests are especially powerful, as they allow us to simulate the actions of
a user interacting with our application using a web browser. Integration tests
will eventually be our primary testing technique, but controller tests give us an
easier place to start.
146 CHAPTER 3. MOSTLY STATIC PAGES
3.3.1 Our first test
Now it’s time to add an About page to our application. As we’ll see, the test is
short and simple, so we’ll follow the guidelines from Box 3.3 and write the test
first. We’ll then use the failing test to drive the writing of the application code.
Getting started with testing can be challenging, requiring extensive knowl-
edge of both Rails and Ruby. At this early stage, writing tests might thus seem
hopelessly intimidating. Luckily, Rails has already done the hardest part for
us, because rails generate controller (Listing 3.7) automatically gen-
erated a test file to get us started:
$ ls test/controllers/
static_pages_controller_test.rb
Let’s take a look at it (Listing 3.14).
Listing 3.14: The default tests for the StaticPages controller. green
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
end
test "should get help" do
get static_pages_help_url
assert_response :success
end
end
It’s not important at this point to understand the syntax in Listing 3.14 in de-
tail, but we can see that there are two tests, one for each controller action we
included on the command line in Listing 3.7. Each test simply gets a URL and
verifies (via an assertion) that the result is a success. Here the use of get indi-
cates that our tests expect the Home and Help pages to be ordinary web pages,
3.3. GETTING STARTED WITH TESTING 147
accessed using a GET request (Box 3.2). The response :success is an abstract
representation of the underlying HTTP status code (in this case, 200 OK). In
other words, a test like
test "should get home" do
get static_pages_home_url
assert_response :success
end
says “Let’s test the Home page by issuing a GET request to the Static Pages
home URL and then making sure we receive a success’ status code in re-
sponse.”
To begin our testing cycle, we need to run our test suite to verify that the
tests currently pass. We can do this with the rails command as follows:
Listing 3.15: green
$ rails db:migrate # Necessary on some systems
$ rails test
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
As required, initially our test suite is passing (green). (Some systems won’t
actually display the color green unless you add the minitest reporters from the
optional Section 3.6.1, but the terminology is common even when literal colors
aren’t involved.) Note that here and throughout this tutorial, I’ll generally omit
some lines from the test output in order to highlight only the most imporant
parts.
By the way, on some systems you may see generated files of the form
/db/test.sqlite3-0
show up in the db directory. To prevent these generated les from being added
to the repository, I suggest adding a rule to the .gitignore file (Section 1.3.1)
to ignore them, as shown in Listing 3.16.
148 CHAPTER 3. MOSTLY STATIC PAGES
Listing 3.16: Ignoring generated database files.
.gitignore
.
.
.
# Ignore db test files.
db/test.*
3.3.2 Red
As noted in Box 3.3, test-driven development involves writing a failing test
first, writing the application code needed to get it to pass, and then refactoring
the code if necessary. Because many testing tools represent failing tests with
the color red and passing tests with the color green, this sequence is sometimes
known as the “Red, Green, Refactor” cycle. In this section, we’ll complete the
first step in this cycle, getting to red by writing a failing test. Then we’ll get to
green in Section 3.3.3, and refactor in Section 3.4.3.
9
Our first step is to write a failing test for the About page. By following the
models from Listing 3.14, can you guess what it should be? The answer appears
in Listing 3.17.
Listing 3.17: A test for the About page. red
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
end
test "should get help" do
get static_pages_help_url
assert_response :success
9
On some systems, rails test shows red when the tests fail but doesn’t show green when the tests pass. To
arrange for a true Red–Green cycle, see Section 3.6.1.
3.3. GETTING STARTED WITH TESTING 149
end
test "should get about" do
get static_pages_about_url
assert_response :success
end
end
We see from the highlighted lines in Listing 3.17 that the test for the About
page is the same as the Home and Help tests with the word “about” in place of
“home” or “help”.
As required, the test initially fails:
Listing 3.18: red
$ rails test
3 tests, 2 assertions, 0 failures, 1 errors, 0 skips
3.3.3 Green
Now that we have a failing test (red), we’ll use the failing test’s error messages
to guide us to a passing test (green), thereby implementing a working About
page.
We can get started by examining the error message output by the failing test:
Listing 3.19: red
$ rails test
NameError: undefined local variable or method `static_pages_about_url'
The error message here says that the Rails code for the About page URL is
undefined, which is a hint that we need to add a line to the routes file. We can
accomplish this by following the pattern in Listing 3.8, as shown in Listing 3.20.
150 CHAPTER 3. MOSTLY STATIC PAGES
Listing 3.20: Adding the about route. red
config/routes.rb
Rails.application.routes.draw do
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
root 'application#hello'
end
The highlighted line in Listing 3.20 tells Rails to route a GET request for the
URL /static_pages/about to the about action in the Static Pages controller. This
automatically creates a helper called
static_pages_about_url
Running our test suite again, we see that it is still red, but now the error
message has changed:
Listing 3.21: red
$ rails test
AbstractController::ActionNotFound:
The action 'about' could not be found for StaticPagesController
The error message now indicates a missing about action in the Static Pages
controller, which we can add by following the model provided by home and
help in Listing 3.9, as shown in Listing 3.22.
Listing 3.22: The Static Pages controller with added about action. red
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
end
3.3. GETTING STARTED WITH TESTING 151
def help
end
def about
end
end
As before, our test suite is still red, but the error message has changed again:
$ rails test
ActionController::UnknownFormat: StaticPagesController#about is missing
a template for this request format and variant.
This indicates a missing template, which in the context of Rails is essentially
the same thing as a view. As described in Section 3.2.1, an action called home
is associated with a view called home.html.erb located in the app/views/-
static_pages directory, which means that we need to create a new file called
about.html.erb in the same directory.
The way to create a le varies by system setup, but most text editors will
let you control-click inside the directory where you want to create the file to
bring up a menu with a “New File” menu item. Alternately, you can use the
File menu to create a new file and then pick the proper directory when saving
it. Finally, you can use my favorite trick by applying the Unix touch command
as follows:
$ touch app/views/static_pages/about.html.erb
As mentioned in Learn Enough Command Line to Be Dangerous, touch is
designed to update the modification timestamp of a file or directory without
otherwise affecting it, but as a side-effect it creates a new (blank) file if one
doesn’t already exist. (If using the cloud IDE, you may have to refresh the
file tree as described in Section 1.2.1. This is a good example of technical
sophistication (Box 1.2).)
Once you’ve created the about.html.erb file in the right directory, you
should fill it with the contents shown in Listing 3.23.
152 CHAPTER 3. MOSTLY STATIC PAGES
Listing 3.23: Code for the About page. green
app/views/static_pages/about.html.erb
<h1>About</h1>
<p>
The <a href="https://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a>, part of the
<a href="https://www.learnenough.com/">Learn Enough</a> family of
tutorials, is a
<a href="https://www.railstutorial.org/book">book</a> and
<a href="https://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="https://rubyonrails.org/">Ruby on Rails</a>.
This is the sample app for the tutorial.
</p>
At this point, running rails test should get us back to green:
Listing 3.24: green
$ rails test
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
Of course, it’s never a bad idea to take a look at the page in a browser to make
sure our tests aren’t leading us astray (Figure 3.7).
3.3.4 Refactor
Now that we’ve gotten to green, we are free to refactor our code with confi-
dence. When developing an application, often code will start to “smell”, mean-
ing that it gets ugly, bloated, or filled with repetition. The computer doesn’t
care what the code looks like, of course, but humans do, so it is important to
keep the code base clean by refactoring frequently. Although our sample app is
a little too small to refactor right now, code smell seeps in at every crack, and
we’ll get started refactoring in Section 3.4.3.
3.3. GETTING STARTED WITH TESTING 153
Figure 3.7: The new About page (/static_pages/about).
154 CHAPTER 3. MOSTLY STATIC PAGES
Page URL Base title Variable title
Home /static_pages/home "Ruby on Rails Tutorial Sample App" "Home"
Help /static_pages/help "Ruby on Rails Tutorial Sample App" "Help"
About /static_pages/about "Ruby on Rails Tutorial Sample App" "About"
Table 3.2: The (mostly) static pages for the sample app.
3.4 Slightly dynamic pages
Now that we’ve created the actions and views for some static pages, we’ll make
them slightly dynamic by adding some content that changes on a per-page ba-
sis: we’ll have the title of each page change to reflect its content. Whether a
changing title represents
truly
dynamic content is debatable, but in any case it
lays the necessary foundation for unambiguously dynamic content in Chapter 7.
Our plan is to edit the Home, Help, and About pages to make page titles
that change on each page. This will involve using the <title> tag in our
page views. Most browsers display the contents of the title tag at the top of the
browser window, and it is also important for search-engine optimization. We’ll
be using the full “Red, Green, Refactor” cycle: rst by adding simple tests for
our page titles (red), then by adding titles to each of our three pages (green),
and finally using a layout file to eliminate duplication (Refactor). By the end
of this section, all three of our static pages will have titles of the form “<page
name> | Ruby on Rails Tutorial Sample App”, where the first part of the title
will vary depending on the page (Table 3.2).
The rails new command (Listing 3.1) creates a layout file by default, but
it’s instructive to ignore it initially, which we can do by changing its name:
$ mv app/views/layouts/application.html.erb layout_file # temporary change
You wouldn’t normally do this in a real application, but it’s easier to understand
the purpose of the layout file if we start by disabling it.
3.4. SLIGHTLY DYNAMIC PAGES 155
3.4.1 Testing titles (Red)
To add page titles, we need to learn (or review) the structure of a typical web
page, which takes the form shown in Listing 3.25. (This is covered in much
more depth in Learn Enough HTML to Be Dangerous.)
Listing 3.25: The HTML structure of a typical web page.
<!DOCTYPE html>
<html>
<head>
<title>Greeting</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
The structure in Listing 3.25 includes a document type, or doctype, declaration
at the top to tell browsers which version of HTML we’re using (in this case,
HTML5);
10
a head section, in this case with “Greeting” inside a title tag;
and a body section, in this case with “Hello, world!” inside a p (paragraph)
tag. (The indentation is optional—HTML is not sensitive to whitespace, and
ignores both tabs and spaces—but it makes the document’s structure easier to
see.)
We’ll write simple tests for each of the titles in Table 3.2 by combining the
tests in Listing 3.17 with the assert_select method, which lets us test for
the presence of a particular HTML tag (sometimes called a “selector”, hence
the name):
11
assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
In particular, the code above checks for the presence of a <title> tag con-
taining the string “Home | Ruby on Rails Tutorial Sample App”. Applying this
10
HTML changes with time; by explicitly making a doctype declaration we make it likelier that browsers will
render our pages properly in the future. The simple doctype <!DOCTYPE html> is characteristic of the latest
HTML standard, HTML5.
11
For a list of common minitest assertions, see the table of available assertions in the Rails Guides testing article.
156 CHAPTER 3. MOSTLY STATIC PAGES
idea to all three static pages gives the tests shown in Listing 3.26.
Listing 3.26: The Static Pages controller test with title tests. red
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
end
test "should get help" do
get static_pages_help_url
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
test "should get about" do
get static_pages_about_url
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
end
With the tests from Listing 3.26 in place, you should verify that the test suite
is currently red:
Listing 3.27: red
$ rails test
3 tests, 6 assertions, 3 failures, 0 errors, 0 skips
3.4.2 Adding page titles (Green)
Now we’ll add a title to each page, getting the tests from Section 3.4.1 to pass
in the process. Applying the basic HTML structure from Listing 3.25 to the
custom Home page from Listing 3.12 yields Listing 3.28.
3.4. SLIGHTLY DYNAMIC PAGES 157
Listing 3.28: The view for the Home page with full HTML structure. red
app/views/static_pages/home.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Home | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
</body>
</html>
The corresponding web page appears in Figure 3.8. Note that the browser used
in the screenshots (Safari) displays the page title only if you include an addi-
tional tab, which explains the second tab shown in Figure 3.8.
Following this model for the Help page (Listing 3.13) and the About page
(Listing 3.23) yields the code in Listing 3.29 and Listing 3.30.
Listing 3.29: The view for the Help page with full HTML structure. red
app/views/static_pages/help.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Help | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<
h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="https://www.railstutorial.org/help">Rails Tutorial help
page</a>.
To get help on this sample app, see the
<a href="https://www.railstutorial.org/book"><em>Ruby on Rails
Tutorial</em> book</a>.
</p>
</body>
</html>
158 CHAPTER 3. MOSTLY STATIC PAGES
Figure 3.8: The Home page with a title.
3.4. SLIGHTLY DYNAMIC PAGES 159
Listing 3.30: The view for the About page with full HTML structure. green
app/views/static_pages/about.html.erb
<!DOCTYPE html>
<html>
<head>
<title>About | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>About</h1>
<p>
The <a href="https://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a>, part of the
<a href="https://www.learnenough.com/">Learn Enough</a> family of
tutorials, is a
<a href="https://www.railstutorial.org/book">book</a> and
<a href="https://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="https://rubyonrails.org/">Ruby on Rails</a>.
This is the sample app for the tutorial.
</p>
</body>
</html>
At this point, the test suite should be back to green:
Listing 3.31: green
$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
Beginning in this section, we’ll start making modifications to the applica-
tions in the exercises that won’t generally be reflected in future code listings.
The reason is so that the text makes sense to readers who don’t complete the
exercises, but as a result your code will diverge from the main text if you do
160 CHAPTER 3. MOSTLY STATIC PAGES
solve them. Learning to resolve small discrepancies like this is an excellent
example of technical sophistication (Box 1.2).
1. You may have noticed some repetition in the Static Pages controller test
(Listing 3.26). In particular, the base title, “Ruby on Rails Tutorial Sam-
ple App”, is the same for every title test. Using the special function
setup, which is automatically run before every test, verify that the tests
in Listing 3.32 are still green. (Listing 3.32 uses an instance variable,
seen briefly in Section 2.2.2 and covered further in Section 4.4.5, com-
bined with string interpolation, which is covered further in Section 4.2.1.)
Listing 3.32: The Static Pages controller test with a base title. green
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
def setup
@base_title = "Ruby on Rails Tutorial Sample App"
end
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Home | #{@base_title}"
end
test "should get help" do
get static_pages_help_url
assert_response :success
assert_select "title", "Help | #{@base_title}"
end
test "should get about" do
get static_pages_about_url
assert_response :success
assert_select "title", "About | #{@base_title}"
end
end
3.4. SLIGHTLY DYNAMIC PAGES 161
3.4.3 Layouts and embedded Ruby (Refactor)
We’ve achieved a lot already in this section, generating three valid pages using
Rails controllers and actions, but they are purely static HTML and hence don’t
show off the power of Rails. Moreover, they suffer from terrible duplication:
The page titles are almost (but not quite) exactly the same.
“Ruby on Rails Tutorial Sample App” is common to all three titles.
The entire HTML skeleton structure is repeated on each page.
This repeated code is a violation of the important “Don’t Repeat Yourself”
(DRY) principle; in this section we’ll “DRY out our code” by removing the
repetition. At the end, we’ll re-run the tests from Section 3.4.2 to verify that the
titles are still correct.
Paradoxically, we’ll take the first step toward eliminating duplication by
first adding some more: we’ll make the titles of the pages, which are currently
quite similar, match exactly. This will make it much simpler to remove all the
repetition at a stroke.
The technique involves using embedded Ruby in our views. Since the
Home, Help, and About page titles have a variable component, we’ll use a spe-
cial Rails function called provide to set a different title on each page. We can
see how this works by replacing the literal title “Home” in the home.html.erb
view with the code in Listing 3.33.
Listing 3.33: The view for the Home page with an embedded Ruby title. green
app/views/static_pages/home.html.erb
<% provide(:title, "Home") %>
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Sample App</h1>
<p>
162 CHAPTER 3. MOSTLY STATIC PAGES
This is the home page for the
<a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
</body>
</html>
Listing 3.33 is our first example of embedded Ruby, also called ERb (or ERB).
(Now you know why HTML views have the file extension .html.erb.) ERb
is the primary template system for including dynamic content in web pages.
12
The code
<% provide(:title, "Home") %>
indicates using <% ... %> that Rails should call the provide function and
associate the string "Home" with the label :title.
13
Then, in the title, we
use the closely related notation <%= ... %> to insert the title into the template
using Ruby’s yield function:
14
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
(The distinction between the two types of embedded Ruby is that <% ... %>
executes the code inside, while <%= ... %> executes it and inserts the result
into the template.) The resulting page is exactly the same as before, only now
the variable part of the title is generated dynamically by ERb.
We can verify that all this works by running the tests from Section 3.4.2 and
see that they are still green:
12
There is a second popular template system called Haml (note: not “HAML”), which I personally love, but it’s
not quite standard enough for use in an introductory tutorial.
13
Experienced Rails developers might have expected the use of content_for at this point, but it doesn’t work
well with the asset pipeline. The provide function is its replacement.
14
If you’ve studied Ruby before, you might suspect that Rails is yielding the contents to a block, and your
suspicion would be correct. But you don’t need to know this to develop applications with Rails.
3.4. SLIGHTLY DYNAMIC PAGES 163
Listing 3.34: green
$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
Then we can make the corresponding replacements for the Help and About
pages (Listing 3.35 and Listing 3.36).
Listing 3.35: The view for the Help page with an embedded Ruby title. green
app/views/static_pages/help.html.erb
<% provide(:title, "Help") %>
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="https://www.railstutorial.org/help">Rails Tutorial help
section</a>.
To get help on this sample app, see the
<a href="https://www.railstutorial.org/book"><em>Ruby on Rails
Tutorial</em> book</a>.
</p>
</body>
</html>
Listing 3.36: The view for the About page with an embedded Ruby title.
green
app/views/static_pages/about.html.erb
<% provide(:title, "About") %>
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>About</h1>
<p>
164 CHAPTER 3. MOSTLY STATIC PAGES
The <a href="https://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a>, part of the
<a href="https://www.learnenough.com/">Learn Enough</a> family of
tutorials, is a
<a href="https://www.railstutorial.org/book">book</a> and
<a href="https://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="https://rubyonrails.org/">Ruby on Rails</a>.
This is the sample app for the tutorial.
</p>
</body>
</html>
Now that we’ve replaced the variable part of the page titles with ERb, each
of our pages looks something like this:
<% provide(:title, "Page Title") %>
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
Contents
</body>
</html>
In other words, all the pages are identical in structure, including the contents of
the title tag, with the sole exception of the material inside the body tag.
In order to factor out this common structure, Rails comes with a special lay-
out file called application.html.erb, which we renamed in the beginning
of this section (Section 3.4) and which we’ll now restore:
$ mv layout_file app/views/layouts/application.html.erb
To get the layout to work, we have to replace the default title with the em-
bedded Ruby from the examples above:
3.4. SLIGHTLY DYNAMIC PAGES 165
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
The resulting layout appears in Listing 3.37.
Listing 3.37: The sample application site layout. green
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
<meta charset="utf-8">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
</body>
</html>
Note here the special line
<%= yield %>
This code is responsible for inserting the contents of each page into the layout.
It’s not important to know exactly how this works; what matters is that using this
layout ensures that, for example, visiting the page /static_pages/home converts
the contents of home.html.erb to HTML and then inserts it in place of <%=
yield %>.
Listing 3.37 also includes the “character set”, which in this case is utf-8 for
displaying Unicode.
Finally, it’s worth noting that the default Rails layout includes several addi-
tional lines:
166 CHAPTER 3. MOSTLY STATIC PAGES
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag ... %>
<%= javascript_pack_tag "application", ... %>
This code arranges to include the application stylesheet and JavaScript, which
are part of the asset pipeline (Section 5.2.1), together with the Rails method
csp_meta_tag, which implements Content Security Policy (CSP) to miti-
gate cross-site scripting (XSS) attacks, and csrf_meta_tags, which mitigates
cross-site request forgery (CSRF) attacks. (One huge advantage of using a ma-
ture framework like Rails is that it worries about such things so that we don’t
have to.)
Even though the tests are passing, there one detail left to deal with: the
views in Listing 3.33, Listing 3.35, and Listing 3.36 are still filled with all the
HTML structure included in the layout. Since it’s redundant (and indeed leads
to invalid HTML markup) we should remove it and leave only the interior con-
tents. The resulting cleaned-up views appear in Listing 3.38, Listing 3.39, and
Listing 3.40.
Listing 3.38: The Home page with HTML structure removed. green
app/views/static_pages/home.html.erb
<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
Listing 3.39: The Help page with HTML structure removed. green
app/views/static_pages/help.html.erb
<% provide(:title, "Help") %>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
3.4. SLIGHTLY DYNAMIC PAGES 167
<a href="https://www.railstutorial.org/help">Rails Tutorial Help page</a>.
To get help on this sample app, see the
<a href="https://www.railstutorial.org/book"><em>Ruby on Rails Tutorial</em>
book</a>.
</p>
Listing 3.40: The About page with HTML structure removed. green
app/views/static_pages/about.html.erb
<% provide(:title, "About") %>
<h1>About</h1>
<p>
The <a href="https://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a>, part of the
<a href="https://www.learnenough.com/">Learn Enough</a> family of
tutorials, is a
<a href="https://www.railstutorial.org/book">book</a> and
<a href="https://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="https://rubyonrails.org/">Ruby on Rails</a>.
This is the sample app for the tutorial.
</p>
With these views defined, the Home, Help, and About pages are the same as
before, but they have much less duplication.
Experience shows that even fairly simple refactoring is error-prone and can
easily go awry. This is one reason why having a good test suite is so valuable.
Rather than double-checking every page for correctness—a procedure that isn’t
too hard early on but rapidly becomes unwieldy as an application grows—we
can simply verify that the test suite is still green:
Listing 3.41: green
$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
This isn’t a proof that our code is still correct, but it greatly increases the prob-
ability, thereby providing a safety net to protect us against future bugs.
168 CHAPTER 3. MOSTLY STATIC PAGES
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Make a Contact page for the sample app.
15
Following the model in List-
ing 3.17, first write a test for the existence of a page at the URL /static_-
pages/contact by testing for the title “Contact | Ruby on Rails Tutorial
Sample App”. Get your test to pass by following the same steps as when
making the About page in Section 3.3.3, including filling the Contact
page with the content from Listing 3.42.
Listing 3.42: Code for a proposed Contact page.
app/views/static_pages/contact.html.erb
<% provide(:title, "Contact") %>
<h1>Contact</h1>
<p>
Contact the Ruby on Rails Tutorial about the sample app at the
<a href="https://www.railstutorial.org/contact">contact page</a>.
</p>
3.4.4 Setting the root route
Now that we’ve customized our site’s pages and gotten a good start on the test
suite, let’s set the applications root route before moving on. As in Section 1.2.4
and Section 2.2.2, this involves editing the routes.rb le to connect / to a
page of our choice, which in this case will be the Home page. (At this point,
I also recommend removing the hello action from the Application controller
if you added it in Section 3.1.) As shown in Listing 3.43, this means changing
the root route from
15
This exercise is solved in Section 5.3.1.
3.4. SLIGHTLY DYNAMIC PAGES 169
root 'application#hello'
to
root 'static_pages#home'
This arranges for requests for / to be routed to the home action in the Static
Pages controller. The resulting routes file is shown in Figure 3.9.
Listing 3.43: Setting the root route to the Home page.
config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
end
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Adding the root route in Listing 3.43 leads to the creation of a Rails helper
called root_url (in analogy with helpers like static_pages_home_-
url). By lling in the code marked FILL_IN in Listing 3.44, write a test
for the root route.
2. Due to the code in Listing 3.43, the test in the previous exercise is al-
ready green. In such a case, it’s harder to be confident that we’re actually
testing what we think we’re testing, so modify the code in Listing 3.43
by commenting out the root route to get to red (Listing 3.45). (We’ll talk
more about Ruby comments in Section 4.2.) Then uncomment it (thereby
restoring the original Listing 3.43) and verify that you get back to green.
170 CHAPTER 3. MOSTLY STATIC PAGES
Figure 3.9: The Home page at the root route.
3.5. CONCLUSION 171
Listing 3.44: A test for the root route. green
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get root" do
get FILL_IN
assert_response FILL_IN
end
test "should get home" do
get static_pages_home_url
assert_response :success
end
test "should get help" do
get static_pages_help_url
assert_response :success
end
test "should get about" do
get static_pages_about_url
assert_response :success
end
end
Listing 3.45: Commenting out the root route to get a failing test. red
config/routes.rb
Rails.application.routes.draw do
# root 'static_pages#home'
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
end
3.5 Conclusion
Seen from the outside, this chapter hardly accomplished anything: we started
with static pages, and ended with… mostly static pages. But appearances are
172 CHAPTER 3. MOSTLY STATIC PAGES
deceiving: by developing in terms of Rails controllers, actions, and views, we
are now in a position to add arbitrary amounts of dynamic content to our site.
Seeing exactly how this plays out is the task for the rest of this tutorial.
Before moving on, let’s take a minute to commit the changes on our topic
branch and merge them into the master branch. Back in Section 3.2 we created
a Git branch for the development of static pages. If you haven’t been making
commits as we’ve been moving along, first make a commit indicating that we’ve
reached a stopping point:
$ git add -A
$ git commit -m "Finish static pages"
Then merge the changes back into the master branch using the same technique
as in Section 1.3.4:
16
$ git checkout master
$ git merge static-pages
Once you reach a stopping point like this, it’s usually a good idea to push
your code up to a remote repository (which, if you followed the steps in Sec-
tion 1.3.3, will be GitHub):
$ git push
I also recommend deploying the application to Heroku:
$ rails test
$ git push heroku
Here we’ve taken care to run the test suite before deploying, which is a good
habit to develop.
16
If you get an error message saying that the Spring process id (pid) file would be overwritten by the merge,
just remove the file using rm -f *.pid at the command line.
3.6. ADVANCED TESTING SETUP 173
3.5.1 What we learned in this chapter
For a third time, we went through the full procedure of creating a new
Rails application from scratch, installing the necessary gems, pushing it
up to a remote repository, and deploying it to production.
The rails script generates a new controller with rails generate
controller ControllerName <optional action names>.
New routes are defined in the file config/routes.rb.
Rails views can contain static HTML or embedded Ruby (ERb).
Automated testing allows us to write test suites that drive the develop-
ment of new features, allow for confident refactoring, and catch regres-
sions.
Test-driven development uses a “Red, Green, Refactor” cycle.
Rails layouts allow the use of a common template for pages in our appli-
cation, thereby eliminating duplication.
3.6 Advanced testing setup
This optional section describes the testing setup used in the Ruby on Rails Tu-
torial screencast series. There are two main elements: an enhanced pass/fail
reporter (Section 3.6.1), and an automated test runner that detects le changes
and automatically runs the corresponding tests (Section 3.6.2). The code in this
section is advanced and is presented for convenience only; you are not expected
to understand it at this time.
The changes in this section should be made on the master branch:
$ git checkout master
174 CHAPTER 3. MOSTLY STATIC PAGES
3.6.1 minitest reporters
Although many systems, including the cloud IDE, will show the appropriate
colors for red and green test suites, adding minitest reporters lends a degree
of pleasant polish to the test outputs, so I recommend adding the code in List-
ing 3.46 to your test helper file,
17
thereby making use of the minitest-re-
porters gem included in Listing 3.2.
Listing 3.46: Configuring the tests to show red and green.
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
require "minitest/reporters"
Minitest::Reporters.use!
class ActiveSupport::TestCase
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
The resulting transition from red to green in the cloud IDE appears as in Fig-
ure 3.10.
3.6.2 Automated tests with Guard
One annoyance associated with using the rails test command is having
to switch to the command line and run the tests by hand. To avoid this in-
convenience, we can use Guard to automate the running of the tests. Guard
monitors changes in the filesystem so that, for example, when we change the
17
The code in Listing 3.46 mixes single- and double-quoted strings. This is because rails new generates
single-quoted strings, whereas the minitest reporters documentation uses double-quoted strings. This mixing of
the two string types is common in Ruby; see Section 4.2.1 for more information.
3.6. ADVANCED TESTING SETUP 175
Figure 3.10: Going from red to green in the cloud IDE.
static_pages_controller_test.rb file, only those tests get run. Even
better, we can configure Guard so that when, say, the home.html.erb file is
modified, the static_pages_controller_test.rb automatically runs.
The Gemfile in Listing 3.2 has already included the guard gem in our
application, so to get started we just need to initialize it:
$ bundle exec guard init
Writing new Guardfile to /home/ec2-user/environment/sample_app/Guardfile
00:51:32 - INFO - minitest guard added to Guardfile, feel free to edit it
We then edit the resulting Guardfile so that Guard will run the right tests
when the integration tests and views are updated, which will look something
like Listing 3.47. For maximum flexibility, I recommend using the version of
the Guardfile listed in the reference application, which if you’re reading this
online should be identical to Listing 3.47:
Reference Guardfile at railstutorial.org/guardfile
Listing 3.47: A custom Guardfile.
# Defines the matching rules for Guard.
guard :minitest, spring: "bin/rails test", all_on_start: false do
176 CHAPTER 3. MOSTLY STATIC PAGES
watch(%r{^test/(.*)/?(.*)_test\.rb$})
watch('test/test_helper.rb') { 'test' }
watch('config/routes.rb') { interface_tests }
watch(%r{app/views/layouts/*}) { interface_tests }
watch(%r{^app/models/(.*?)\.rb$}) do |matches|
"test/models/#{matches[1]}_test.rb"
end
watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches|
resource_tests(matches[1])
end
watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches|
["test/controllers/#{matches[1]}_controller_test.rb"] +
integration_tests(matches[1])
end
watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|
integration_tests(matches[1])
end
watch('app/views/layouts/application.html.erb') do
'test/integration/site_layout_test.rb'
end
watch('app/helpers/sessions_helper.rb') do
integration_tests << 'test/helpers/sessions_helper_test.rb'
end
watch('app/controllers/sessions_controller.rb') do
['test/controllers/sessions_controller_test.rb',
'test/integration/users_login_test.rb']
end
watch('app/controllers/account_activations_controller.rb') do
'test/integration/users_signup_test.rb'
end
watch(%r{app/views/users/*}) do
resource_tests('users') +
['test/integration/microposts_interface_test.rb']
end
end
# Returns the integration tests corresponding to the given resource.
def integration_tests(resource = :all)
if resource == :all
Dir["test/integration/*"]
else
Dir["test/integration/#{resource}_*.rb"]
end
end
# Returns all tests that hit the interface.
def interface_tests
integration_tests << "test/controllers/"
end
# Returns the controller tests corresponding to the given resource.
3.6. ADVANCED TESTING SETUP 177
def controller_test(resource)
"test/controllers/#{resource}_controller_test.rb"
end
# Returns all tests for the given resource.
def resource_tests(resource)
integration_tests(resource) << controller_test(resource)
end
On the cloud IDE, there’s one additional step, which is to run the following
rather obscure commands to allow Guard to monitor all the files in the project:
$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
$ sudo sysctl -p
Once Guard is configured, you should open a new terminal (as with the Rails
server in Section 1.2.2) and run it at the command line as follows (Figure 3.11):
$ bundle exec guard
The rules in Listing 3.47 are optimized for this tutorial, automatically running
(for example) the integration tests when a controller is changed. To run all the
tests, simply hit return at the guard> prompt.
To exit Guard, press Ctrl-D. To add additional matchers to Guard, refer to
the examples in Listing 3.47, the Guard README, and the Guard wiki.
If the test suite fails without apparent cause, try exiting Guard, stopping
Spring (which Rails uses to preload information to help speed up tests), and
restarting:
$ bin/spring stop # Try this if the tests mysteriously start failing.
$ bundle exec guard
Before proceeding, you should add your changes and make a commit:
178 CHAPTER 3. MOSTLY STATIC PAGES
Figure 3.11: Using Guard on the cloud IDE.
$ git add -A
$ git commit -m "Complete advanced testing setup"
Chapter 4
Rails-flavored Ruby
Grounded in examples from Chapter 3, this chapter explores some elements of
the Ruby programming language that are important for Rails. Ruby is a big lan-
guage, but fortunately the subset needed to be productive as a Rails developer is
relatively small. It also differs somewhat from the usual material covered in an
introduction to Ruby. This chapter is designed to give you a solid foundation in
Rails-flavored Ruby, whether or not you have prior experience in the language.
It covers a lot of material, and it’s OK not to get it all on the rst pass. We’ll
refer back to it frequently in future chapters.
1
4.1 Motivation
As we saw in the last chapter, it’s possible to develop the skeleton of a Rails
application, and even start testing it, with essentially no knowledge of the un-
derlying Ruby language. We did this by relying on the test code provided by the
tutorial and addressing each error message until the test suite was passing. This
situation can’t last forever, though, and we’ll open this chapter with an addition
to the site that brings us face-to-face with our Ruby limitations.
As in Section 3.2, we’ll use a separate topic branch to keep our changes
self-contained:
1
For a more systematic introduction to Ruby, see Learn Enough Ruby to Be Dangerous.
179
180 CHAPTER 4. RAILS-FLAVORED RUBY
$ git checkout -b rails-flavored-ruby
We’ll merge our changes into master in Section 4.5.
4.1.1 Built-in helpers
When we last saw our new application, we had just updated our mostly static
pages to use Rails layouts to eliminate duplication in our views, as shown in
Listing 4.1 (which is the same as Listing 3.37).
Listing 4.1: The sample application site layout.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
<meta charset="utf-8">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
</body>
</html>
Let’s focus on one particular line in Listing 4.1:
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
This uses the built-in Rails function stylesheet_link_tag (which you can
4.1. MOTIVATION 181
read more about at the Rails API)
2
to include application.css for all media
types (including computer screens and printers). To an experienced Rails de-
veloper, this line looks simple, but there are at least four potentially confusing
Ruby ideas: built-in Rails methods, method invocation with missing parenthe-
ses, symbols, and hashes. We’ll cover all of these ideas in this chapter.
4.1.2 Custom helpers
In addition to coming equipped with a large number of built-in functions for
use in the views, Rails also allows the creation of new ones. Such functions are
called helpers; to see how to make a custom helper, let’s start by examining the
title line from Listing 4.1:
<%= yield(:title) %> | Ruby on Rails Tutorial Sample App
This relies on the definition of a page title (using provide) in each view, as in
<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
But what if we don’t provide a title? It’s a good convention to have a base title
we use on every page, with an optional page title if we want to be more specific.
We’ve almost achieved that with our current layout, with one wrinkle: as you
can see if you delete the provide call in one of the views, in the absence of a
page-specific title the full title appears as follows:
2
An “API” is an Application Programming Interface, which is a set of methods and other conventions that
serves as an abstraction layer for interacting with a software system. The practical effect is that we as developers
don’t need to understand the program internals; we need only be famliar with the public-facing API. In the present
case, this means that, rather than be concerned with how stylesheet_link_tag is implemented, we need only
know how it behaves.
182 CHAPTER 4. RAILS-FLAVORED RUBY
| Ruby on Rails Tutorial Sample App
In other words, there’s a suitable base title, but there’s also a leading vertical
bar character | at the beginning.
To solve the problem of a missing page title, we’ll define a custom helper
called full_title. The full_title helper returns a base title, “Ruby on
Rails Tutorial Sample App”, if no page title is defined, and adds a vertical bar
preceded by the page title if one is defined (Listing 4.2).
3
Listing 4.2: Defining a full_title helper.
app/helpers/application_helper.rb
module ApplicationHelper
# Returns the full title on a per-page basis.
def full_title(page_title = '')
base_title = "Ruby on Rails Tutorial Sample App"
if page_title.empty?
base_title
else
page_title + " | " + base_title
end
end
end
Now that we have a helper, we can use it to simplify our layout by replacing
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
with
3
If a helper is specific to a particular controller, you should put it in the corresponding helper file; for example,
helpers for the Static Pages controller generally go in app/helpers/static_pages_helper.rb. In our case,
we expect the full_title helper to be used on all the sites pages, and Rails has a special helper file for this
case: app/helpers/application_helper.rb.
4.1. MOTIVATION 183
<title><%= full_title(yield(:title)) %></title>
as seen in Listing 4.3.
Listing 4.3: The site layout with the full_title helper. green
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<meta charset="utf-8">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
</body>
</html>
To put our helper to work, we can eliminate the unnecessary word “Home”
from the Home page, allowing it to revert to the base title. We do this by first
updating our test with the code in Listing 4.4, which updates the previous title
test and adds one to test for the absence of the custom "Home" string in the title.
Listing 4.4: An updated test for the Home page’s title. red
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Ruby on Rails Tutorial Sample App"
end
184 CHAPTER 4. RAILS-FLAVORED RUBY
test "should get help" do
get static_pages_help_url
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
test "should get about" do
get static_pages_about_url
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
end
Let’s run the test suite to verify that one test fails:
4
Listing 4.5: red
$ rails test
3 tests, 6 assertions, 1 failures, 0 errors, 0 skips
To get the test suite to pass, we’ll remove the provide line from the Home
page’s view, as seen in Listing 4.6.
Listing 4.6: The Home page with no custom page title. green
app/views/static_pages/home.html.erb
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
At this point the tests should pass:
4
I’ll generally run the test suite explicitly for completeness, but in practice I usually just use Guard as described
in Section 3.6.2.
4.2. STRINGS AND METHODS 185
Listing 4.7: green
$ rails test
(Previous examples have included partial output of running rails test, in-
cluding the number of passing and failing tests, but for brevity these will usually
be omitted from now on.)
As with the line to include the application stylesheet in Section 4.1.1, the
code in Listing 4.2 may look simple to the eyes of an experienced Rails de-
veloper, but it’s full of important Ruby ideas: modules, method definition, op-
tional method arguments, comments, local variable assignment, booleans, con-
trol flow, string concatenation, and return values. This chapter will cover all of
these ideas as well.
4.2 Strings and methods
Our principal tool for learning Ruby will be the Rails console, a command-line
program for interacting with Rails applications first seen in Section 2.3.3. The
console itself is built on top of interactive Ruby (irb), and thus has access to
the full power of the Ruby language. (As we’ll see in Section 4.4.4, the console
also has access to the Rails environment.)
If you’re using the cloud IDE, there are a couple of irb configuration pa-
rameters I recommend including. Using the simple nano text editor, open a file
called .irbrc in the home directory:
5
$ nano ~/.irbrc
Then fill it with the contents of Listing 4.8, which arranges to simplify the irb
prompt and suppress some annoying auto-indent behavior.
5
The nano editor is easier for beginners, but for this sort of short edit I would almost always use Vim instead.
To learn Minimum Viable Vim™, see Learn Enough Text Editor to Be Dangerous.
186 CHAPTER 4. RAILS-FLAVORED RUBY
Listing 4.8: Adding some irb configuration.
~/.irbrc
IRB.conf[:PROMPT_MODE] = :SIMPLE
IRB.conf[:AUTO_INDENT_MODE] = false
Finally, exit nano with Ctrl-X and save ~/.irbrc by typing y to confirm.
We can now start the console at the command line as follows:
$ rails console
Loading development environment
>>
By default, the console starts in a development environment, which is one of
three separate environments defined by Rails (the others are test and produc-
tion). This distinction won’t be important in this chapter, but it will in the future,
and we’ll learn more about environments in Section 7.1.1.
The console is a great learning tool, and you should feel free to explore.
Don’t worry—you (probably) won’t break anything. When using the console,
type Ctrl-C if you get stuck, or type Ctrl-D to exit the console altogether. As
with a regular terminal shell, you can also use up-arrow to retrieve previous
commands, which can be a significant time-saver.
Throughout the rest of this chapter, you might find it helpful to consult the
Ruby API. It’s packed (perhaps even too packed) with information; for exam-
ple, to learn more about Ruby strings you can look at the Ruby API entry for
the String class.
During this discussion, we’ll sometimes use Ruby comments, which start
with the pound sign # (also called the “hash mark” or (more poetically) the
“octothorpe”) and extend to the end of the line. Ruby ignores comments, but
they are useful for human readers (including, often, the original author!). In the
code
4.2. STRINGS AND METHODS 187
# Returns the full title on a per-page basis.
def full_title(page_title = '')
.
.
.
end
the first line is a comment indicating the purpose of the subsequent function
definition.
You don’t ordinarily include comments in console sessions, but for instruc-
tional purposes I’ll include some comments in what follows, like this:
$ rails console
>> 17 + 42 # Integer addition
=> 59
If you follow along in this section by typing or copying-and-pasting commands
into your own console, you can of course omit the comments if you like; the
console will ignore them in any case.
4.2.1 Strings
Strings are probably the most important data structure for web applications,
since web pages ultimately consist of strings of characters sent from the server
to the browser. Let’s start exploring strings with the console:
$ rails console
>> "" # An empty string
=> ""
>> "foo" # A nonempty string
=> "foo"
These are string literals (also called literal strings), created using the double
quote character ". The console prints the result of evaluating each line, which
in the case of a string literal is just the string itself.
We can also concatenate strings with the + operator:
188 CHAPTER 4. RAILS-FLAVORED RUBY
>> "foo" + "bar" # String concatenation
=> "foobar"
Here the result of evaluating "foo" plus "bar" is the string "foobar".
6
Another way to build up strings is via interpolation using the special syntax
#{}:
7
>> first_name = "Michael" # Variable assignment
=> "Michael"
>> "#{first_name} Hartl" # String interpolation
=> "Michael Hartl"
Here we’ve assigned the value "Michael" to the variable first_name and
then interpolated it into the string "#{first_name} Hartl". We could also
assign both strings a variable name:
>> first_name = "Michael"
=> "Michael"
>> last_name = "Hartl"
=> "Hartl"
>> first_name + " " + last_name # Concatenation, with a space in between
=> "Michael Hartl"
>> "#{first_name} #{last_name}" # The equivalent interpolation
=> "Michael Hartl"
Note that the final two expressions are equivalent, but I prefer the interpolated
version; having to add the single space " " seems a bit awkward.
Printing
To print a string to the screen, the most commonly used Ruby function is puts
(pronounced “put ess”, for put string”, though some people do pronounce it
like the word “puts” instead):
6
For more on the origins of “foo” and “bar”—and, in particular, the possible non-relation of “foobar” to
“FUBAR”—see the Jargon File entry on “foo”.
7
Programmers familiar with Perl or PHP should compare this to the automatic interpolation of dollar sign
variables in expressions like "foo $bar".
4.2. STRINGS AND METHODS 189
>> puts "foo" # put string
foo
=> nil
The puts method operates as a side-effect: the expression puts "foo" prints
the string to the screen and then returns literally nothing: nil is a special Ruby
value for “nothing at all”. (In what follows, I’ll sometimes suppress the =>
nil part for simplicity.)
As seen in the examples above, using puts automatically includes a new
line after the string gets printed (the same as the behavior of the echo command
covered in Learn Enough Command Line to Be Dangerous). The closely related
print command prints the raw string without the extra line:
>> print "foo" # print string without extra line
foo=> nil
You can see here that the output foo bumps right up against the prompt in the
second line.
The technical name for an extra line of blank space is a newline, typically
represented by “backslash n” \n. We can arrange for print to replicate the
behavior of puts by including an explicit newline character in the string:
>> print "foo\n" # Same as puts "foo"
foo
=> nil
Single-quoted strings
All the examples so far have used double-quoted strings, but Ruby also supports
single-quoted strings. For many uses, the two types of strings are effectively
identical:
190 CHAPTER 4. RAILS-FLAVORED RUBY
>> 'foo' # A single-quoted string
=> "foo"
>> 'foo' + 'bar'
=> "foobar"
There’s an important difference, though; Ruby won’t interpolate into
single-quoted strings:
>> '#{foo} bar' # Single-quoted strings don't allow interpolation
=> "\#{foo} bar"
Note how the console returns values using double-quoted strings, which re-
quires a backslash to escape special character combinations such as #{.
If double-quoted strings can do everything that single-quoted strings can
do, and interpolate to boot, what’s the point of single-quoted strings? They are
often useful because they are truly literal, containing exactly the characters you
type. For example, the “backslash” character is special on most systems, as
in the literal newline \n. If you want a variable to contain a literal backslash,
single quotes make it easier:
>> '\n' # A literal 'backslash n' combination
=> "\\n"
As with the #{ combination in our previous example, Ruby needs to escape the
backslash with an additional backslash; inside double-quoted strings, a literal
backslash is represented with two backslashes. For a small example like this,
there’s not much savings, but if there are lots of things to escape it can be a real
help:
>> 'Newlines (\n) and tabs (\t) both use the backslash character \.'
=> "Newlines (\\n) and tabs (\\t) both use the backslash character \\."
Finally, it’s worth noting that, in the common case that both single and dou-
ble quotes work just fine, you’ll often find that the source code switches between
the two without any apparent pattern. There’s really nothing to be done about
this, except to say, “Welcome to Ruby! You’ll get used to it soon enough.”
4.2. STRINGS AND METHODS 191
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Assign variables city and state to your current city and state of resi-
dence. (If residing outside the U.S., substitute the analogous quantities.)
2. Using interpolation, print (using puts) a string consisting of the city and
state separated by a comma and a space, as in “Los Angeles, CA”.
3. Repeat the previous exercise but with the city and state separated by a tab
character.
4. What is the result if you replace double quotes with single quotes in the
previous exercise?
4.2.2 Objects and message passing
Everything in Ruby, including strings and even nil, is an
object
. We’ll see
the technical meaning of this in Section 4.4.2, but I don’t think anyone ever
understood objects by reading the definition in a book; you have to build up
your intuition for objects by seeing lots of examples.
It’s easier to describe what objects do, which is respond to messages. An
object like a string, for example, can respond to the message length, which
returns the number of characters in the string:
>> "foobar".length # Passing the "length" message to a string
=> 6
Typically, the messages that get passed to objects are methods, which are func-
tions defined on those objects.
8
Strings also respond to the empty? method:
8
Apologies in advance for switching haphazardly between function and method throughout this chapter; in
Ruby, they’re the same thing: all methods are functions, and all functions are methods, because everything is an
object.
192 CHAPTER 4. RAILS-FLAVORED RUBY
>> "foobar".empty?
=> false
>> "".empty?
=> true
Note the question mark at the end of the empty? method. This is a Ruby con-
vention indicating that the return value is boolean: true or false. Booleans
are especially useful for control flow:
>> s = "foobar"
>> if s.empty?
>> "The string is empty"
>> else
>> "The string is nonempty"
>> end
=> "The string is nonempty"
To include more than one clause, we can use elsif (else + if):
>> if s.nil?
>> "The variable is nil"
>> elsif s.empty?
>> "The string is empty"
>> elsif s.include?("foo")
>> "The string includes 'foo'"
>> end
=> "The string includes 'foo'"
Booleans can also be combined using the && (“and”), || (“or”), and ! (“not”)
operators:
>> x = "foo"
=> "foo"
>> y = ""
=> ""
>> puts "Both strings are empty" if x.empty? && y.empty?
=> nil
>> puts "One of the strings is empty" if x.empty? || y.empty?
"One of the strings is empty"
=> nil
>> puts "x is not empty" if !x.empty?
"x is not empty"
=> nil
4.2. STRINGS AND METHODS 193
Since everything in Ruby is an object, it follows that nil is an object, so it
too can respond to methods. One example is the to_s method that can convert
virtually any object to a string:
>> nil.to_s
=> ""
This certainly appears to be an empty string, as we can verify by passing mul-
tiple methods to nil, a technique known as method chaining:
>> nil.empty?
NoMethodError: undefined method `empty?' for nil:NilClass
>> nil.to_s.empty? # Message chaining
=> true
We see here that the nil object doesn’t itself respond to the empty? method,
but nil.to_s does.
There’s a special method for testing for nil-ness, which you might be able
to guess:
>> "foo".nil?
=> false
>> "".nil?
=> false
>> nil.nil?
=> true
The code
puts "x is not empty" if !x.empty?
also shows an alternate use of the if keyword: Ruby allows you to write a
statement that is evaluated only if the statement following if is true. There’s a
complementary unless keyword that works the same way:
194 CHAPTER 4. RAILS-FLAVORED RUBY
>> string = "foobar"
>> puts "The string '#{string}' is nonempty." unless string.empty?
The string 'foobar' is nonempty.
=> nil
It’s worth noting that the nil object is special, in that it is the only Ruby
object that is false in a boolean context, apart from false itself. We can see this
using !! (read “bang bang”), which negates an object twice, thereby coercing
it to its boolean value:
>> !!nil
=> false
In particular, all other Ruby objects are true, even 0:
>> !!0
=> true
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. What is the length of the string “racecar”?
2. Confirm using the reverse method that the string in the previous exer-
cise is the same when its letters are reversed.
3. Assign the string “racecar” to the variable s. Confirm using the compar-
ison operator == that s and s.reverse are equal.
4. What is the result of running the code shown in Listing 4.9? How does it
change if you reassign the variable s to the string onomatopoeia”? Hint:
Use up-arrow to retrieve and edit previous commands
4.2. STRINGS AND METHODS 195
Listing 4.9: A simple palindrome test.
>> puts "It's a palindrome!" if s == s.reverse
4.2.3 Method definitions
The console allows us to define methods the same way we did with the home
action from Listing 3.9 or the full_title helper from Listing 4.2. (Defining
methods in the console is a bit cumbersome, and ordinarily you would use a
file, but it’s convenient for demonstration purposes.) For example, let’s define a
function string_message that takes a single argument and returns a message
based on whether the argument is empty or not:
>> def string_message(str = '')
>> if str.empty?
>> "It's an empty string!"
>> else
>> "The string is nonempty."
>> end
>> end
=> :string_message
>> puts string_message("foobar")
The string is nonempty.
>> puts string_message("")
It's an empty string!
>> puts string_message
It's an empty string!
As seen in the final example, it’s possible to leave out the argument entirely (in
which case we can also omit the parentheses). This is because the code
def string_message(str = '')
contains a default argument, which in this case is the empty string. This makes
the str argument optional, and if we leave it off it automatically takes the given
default value.
196 CHAPTER 4. RAILS-FLAVORED RUBY
Note that Ruby functions have an implicit return, meaning they return the
last statement evaluated—in this case, one of the two message strings, depend-
ing on whether the method’s argument str is empty or not. Ruby also has an
explicit return option; the following function is equivalent to the one above:
>> def string_message(str = '')
>> return "It's an empty string!" if str.empty?
>> return "The string is nonempty."
>> end
(The alert reader might notice at this point that the second return here is ac-
tually unnecessary—being the last expression in the function, the string "The
string is nonempty." will be returned regardless of the return keyword,
but using return in both places has a pleasing symmetry to it.)
It’s also important to understand that the name of the function argument
is irrelevant as far as the caller is concerned. In other words, the first ex-
ample above could replace str with any other valid variable name, such as
the_function_argument, and it would work just the same:
>> def string_message(the_function_argument = '')
>> if the_function_argument.empty?
>> "It's an empty string!"
>> else
>> "The string is nonempty."
>> end
>> end
=> nil
>> puts string_message("")
It's an empty string!
>> puts string_message("foobar")
The string is nonempty.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
4.2. STRINGS AND METHODS 197
1. By replacing FILL_IN with the appropriate comparison test shown in
Listing 4.10, define a method for testing palindromes. Hint: Use the
comparison shown in Listing 4.9.
2. By running your palindrome tester on “racecar” and “onomatopoeia”,
confirm that the first is a palindrome and the second isn’t.
3. By calling the nil? method on palindrome_tester("racecar"),
confirm that its return value is nil (i.e., calling nil? on the result of the
method should return true). This is because the code in Listing 4.10
prints its responses instead of returning them.
Listing 4.10: A simple tester for palindromes.
>> def palindrome_tester(s)
>> if FILL_IN
>> puts "It's a palindrome!"
>> else
>> puts "It's not a palindrome."
>> end
>> end
4.2.4 Back to the title helper
We are now in a position to understand the full_title helper from List-
ing 4.2,
9
which appears with commented annotations in Listing 4.11.
Listing 4.11: An annotated title_helper.
app/helpers/application_helper.rb
module ApplicationHelper
# Returns the full title on a per-page basis. # Documentation comment
def full_title(page_title = '') # Method def, optional arg
9
Well, there will still be one thing left that we don’t understand, which is how Rails ties this all together:
mapping URLs to actions, making the full_title helper available in views, etc. This is an interesting subject,
and I encourage you to investigate it further, but knowing exactly how Rails works is not necessary when using
Rails.
198 CHAPTER 4. RAILS-FLAVORED RUBY
base_title = "Ruby on Rails Tutorial Sample App" # Variable assignment
if page_title.empty? # Boolean test
base_title # Implicit return
else
page_title + " | " + base_title # String concatenation
end
end
end
These elements—function definition (with an optional argument), variable
assignment, boolean tests, control flow, and string concatenation
10
—come to-
gether to make a compact helper method for use in our site layout. The final
element is module ApplicationHelper: modules give us a way to pack-
age together related methods, which can then be mixed in to Ruby classes using
include. When writing ordinary Ruby, you often write modules and include
them explicitly yourself, but in the case of a helper module Rails handles the
inclusion for us. The result is that the full_title method is automagically
available in all our views.
4.3 Other data structures
Although web apps are ultimately about strings, actually making those strings
requires using other data structures as well. In this section, we’ll learn about
some Ruby data structures important for writing Rails applications.
4.3.1 Arrays and ranges
An array is just a list of elements in a particular order. We haven’t discussed
arrays yet in the Rails Tutorial, but understanding them gives a good foundation
for understanding hashes (Section 4.3.3) and for aspects of Rails data modeling
10
It’s tempting to use string interpolation instead—indeed, this was the technique used in all previous versions
of the tutorial—but in fact the call to provide converts the string into a so-called SafeBuffer object instead of
an ordinary string. Interpolating and inserting into a view template then over-escapes any inserted HTML, so a
title such as “Help’s on the way” would be converted to “Help&amp;#39;s on the way”. (Thanks to reader Jeremy
Fleischman for pointing out this subtle issue.)
4.3. OTHER DATA STRUCTURES 199
(such as the has_many association seen in Section 2.3.3 and covered more in
Section 13.1.3).
So far we’ve spent a lot of time understanding strings, and there’s a natural
way to get from strings to arrays using the split method:
>> "foo bar baz".split # Split a string into a three-element array.
=> ["foo", "bar", "baz"]
The result of this operation is an array of three strings. By default, split
divides a string into an array by splitting on whitespace, but you can split on
nearly anything else as well:
>> "fooxbarxbaz".split('x')
=> ["foo", "bar", "baz"]
As is conventional in most computer languages, Ruby arrays are zero-offset,
which means that the first element in the array has index 0, the second has
index 1, and so on:
>> a = [42, 8, 17]
=> [42, 8, 17]
>> a[0] # Ruby uses square brackets for array access.
=> 42
>> a[1]
=> 8
>> a[2]
=> 17
>> a[-1] # Indices can even be negative!
=> 17
We see here that Ruby uses square brackets to access array elements. In addition
to this bracket notation, Ruby offers synonyms for some commonly accessed
elements:
11
11
The second method used here isn’t currently part of Ruby itself, but rather is added by Rails. It works in this
case because the Rails console automatically includes the Rails extensions to Ruby.
200 CHAPTER 4. RAILS-FLAVORED RUBY
>> a # Just a reminder of what 'a' is
=> [42, 8, 17]
>> a.first
=> 42
>> a.second
=> 8
>> a.last
=> 17
>> a.last == a[-1] # Comparison using ==
=> true
This last line introduces the equality comparison operator ==, which Ruby
shares with many other languages, along with the associated != (“not equal”),
etc.:
>> x = a.length # Like strings, arrays respond to the 'length' method.
=> 3
>> x == 3
=> true
>> x == 1
=> false
>> x != 1
=> true
>> x >= 1
=> true
>> x < 1
=> false
In addition to length (seen in the first line above), arrays respond to a
wealth of other methods:
>> a
=> [42, 8, 17]
>> a.empty?
=> false
>> a.include?(42)
=> true
>> a.sort
=> [8, 17, 42]
>> a.reverse
=> [17, 8, 42]
>> a.shuffle
=> [17, 42, 8]
>> a
=> [42, 8, 17]
4.3. OTHER DATA STRUCTURES 201
Note that none of the methods above changes a itself. To mutate the array, use
the corresponding “bang” methods (so-called because the exclamation point is
usually pronounced “bang” in this context):
>> a
=> [42, 8, 17]
>> a.sort!
=> [8, 17, 42]
>> a
=> [8, 17, 42]
You can also add to arrays with the push method or its equivalent operator,
<<, called the “shovel operator”:
>> a.push(6) # Pushing 6 onto an array
=> [42, 8, 17, 6]
>> a << 7 # Pushing 7 onto an array
=> [42, 8, 17, 6, 7]
>> a << "foo" << "bar" # Chaining array pushes
=> [42, 8, 17, 6, 7, "foo", "bar"]
This last example shows that you can chain pushes together, and also that, unlike
arrays in many other languages, Ruby arrays can contain a mixture of different
types (in this case, integers and strings).
Before we saw split convert a string to an array. We can also go the other
way with the join method:
>> a
=> [42, 8, 17, 6, 7, "foo", "bar"]
>> a.join # Join on nothing.
=> "4281767foobar"
>> a.join(', ') # Join on comma-space.
=> "42, 8, 17, 6, 7, foo, bar"
Closely related to arrays are ranges, which can probably most easily be
understood by converting them to arrays using the to_a method:
202 CHAPTER 4. RAILS-FLAVORED RUBY
>> 0..9
=> 0..9
>> 0..9.to_a # Oops, call to_a on 9.
NoMethodError: undefined method `to_a' for 9:Fixnum
>> (0..9).to_a # Use parentheses to call to_a on the range.
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Though 0..9 is a valid range, the second expression above shows that we need
to add parentheses to call a method on it.
Ranges are useful for pulling out array elements:
>> a = %w[foo bar baz quux] # Use %w to make a string array.
=> ["foo", "bar", "baz", "quux"]
>> a[0..2]
=> ["foo", "bar", "baz"]
A particularly useful trick is to use the index -1 at the end of the range to select
every element from the starting point to the end of the array without explicitly
having to use the array’s length:
>> a = (0..9).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>> a[2..(a.length-1)] # Explicitly use the array's length.
=> [2, 3, 4, 5, 6, 7, 8, 9]
>> a[2..-1] # Use the index -1 trick.
=> [2, 3, 4, 5, 6, 7, 8, 9]
Ranges also work with characters:
>> ('a'..'e').to_a
=> ["a", "b", "c", "d", "e"]
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
4.3. OTHER DATA STRUCTURES 203
1. Assign a to be to the result of splitting the string “A man, a plan, a canal,
Panama” on comma-space.
2. Assign s to the string resulting from joining a on nothing.
3. Split s on whitespace and rejoin on nothing. Use the palindrome test
from Listing 4.10 to confirm that the resulting string s is not a palin-
drome by the current definition. Using the downcase method, show that
s.downcase is a palindrome.
4. What is the result of selecting element 7 from the range of letters a
through z? What about the same range reversed? Hint: In both cases
you will have to convert the range to an array.
4.3.2 Blocks
Both arrays and ranges respond to a host of methods that accept blocks, which
are simultaneously one of Ruby’s most powerful and most confusing features:
>> (1..5).each { |i| puts 2 * i }
2
4
6
8
10
=> 1..5
This code calls the each method on the range (1..5) and passes it the block {
|i| puts 2 * i }. The vertical bars around the variable name in |i| are
Ruby syntax for a block variable, and it’s up to the method to know what to do
with the block. In this case, the range’s each method can handle a block with
a single local variable, which we’ve called i, and it just executes the block for
each value in the range.
Curly braces are one way to indicate a block, but there is a second way as
well:
204 CHAPTER 4. RAILS-FLAVORED RUBY
>> (1..5).each do |i|
?> puts 2 * i
>> end
2
4
6
8
10
=> 1..5
Blocks can be more than one line, and often are. In the Rails Tutorial we’ll
follow the common convention of using curly braces only for short one-line
blocks and the do..end syntax for longer one-liners and for multi-line blocks:
>> (1..5).each do |number|
?> puts 2 * number
>> puts '--'
>> end
2
--
4
--
6
--
8
--
10
--
=> 1..5
Here I’ve used number in place of i just to emphasize that any variable name
will do.
Unless you already have a substantial programming background, there is no
shortcut to understanding blocks; you just have to see them a lot, and eventually
you’ll get used to them.
12
Luckily, humans are quite good at making general-
izations from concrete examples; here are a few more, including a couple using
the map method:
12
Programming experts, on the other hand, might benefit from knowing that blocks are closures, which are
one-shot anonymous functions with data attached.
4.3. OTHER DATA STRUCTURES 205
>> 3.times { puts "Betelgeuse!" } # 3.times takes a block with no variables.
"Betelgeuse!"
"Betelgeuse!"
"Betelgeuse!"
=> 3
>> (1..5).map { |i| i**2 } # The ** notation is for 'power'.
=> [1, 4, 9, 16, 25]
>> %w[a b c] # Recall that %w makes string arrays.
=> ["a", "b", "c"]
>> %w[a b c].map { |char| char.upcase }
=> ["A", "B", "C"]
>> %w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]
As you can see, the map method returns the result of applying the given block to
each element in the array or range. In the final two examples, the block inside
map involves calling a particular method on the block variable, and in this case
there’s a commonly used shorthand called “symbol-to-proc”:
>> %w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]
>> %w[A B C].map(&:downcase)
=> ["a", "b", "c"]
(This strange-looking but compact code uses a symbol, which we’ll discuss in
Section 4.3.3.) One interesting thing about this construction is that it was orig-
inally added to Ruby on Rails, and people liked it so much that it has now been
incorporated into core Ruby.
As one final example of blocks, we can take a look at an individual test from
the file in Listing 4.4:
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Ruby on Rails Tutorial Sample App"
end
It’s not important to understand the details (and in fact I don’t know the details
offhand), but we can infer from the presence of the do keyword that the body of
206 CHAPTER 4. RAILS-FLAVORED RUBY
the test is a block. The test method takes in a string argument (the description)
and a block, and then executes the body of the block as part of running the test
suite.
By the way, we’re now in a position to understand the line of Ruby I threw
into Section 1.4.2 to generate random subdomains:
13
('a'..'z').to_a.shuffle[0..7].join
Let’s build it up step-by-step:
>> ('a'..'z').to_a # An alphabet array
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
>> ('a'..'z').to_a.shuffle # Shuffle it.
=> ["c", "g", "l", "k", "h", "z", "s", "i", "n", "d", "y", "u", "t", "j", "q",
"b", "r", "o", "f", "e", "w", "v", "m", "a", "x", "p"]
>> ('a'..'z').to_a.shuffle[0..7] # Pull out the first eight elements.
=> ["f", "w", "i", "a", "h", "p", "c", "x"]
>> ('a'..'z').to_a.shuffle[0..7].join # Join them together to make one string.
=> "mznpybuj"
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Using the range 0..16, print out the first 17 powers of 2.
2. Define a method called yeller that takes in an array of characters and
returns a string with an ALLCAPS version of the input. Verify that yel-
ler(['o', 'l', 'd']) returns "OLD". Hint: Combine map, up-
case, and join.
13
As noted in Chapter 1, in this case the code ('a'..'z').to_a.sample(8).join is an even more compact
way of getting the same result.
4.3. OTHER DATA STRUCTURES 207
3. Define a method called random_subdomain that returns a randomly
generated string of eight letters.
4. By replacing the question marks in Listing 4.12 with the appropriate
methods, combine split, shuffle, and join to write a function that
shuffles the letters in a given string.
Listing 4.12: Skeleton for a string shuffle function.
>> def string_shuffle(s)
>> s.?('').?.?
>> end
>> string_shuffle("foobar")
=> "oobfra"
4.3.3 Hashes and symbols
Hashes are essentially arrays that aren’t limited to integer indices. (In fact, some
languages, especially Perl, sometimes call hashes associative arrays for this
reason.) Instead, hash indices, or keys, can be almost any object. For example,
we can use strings as keys:
>> user = {} # {} is an empty hash.
=> {}
>> user["first_name"] = "Michael" # Key "first_name", value "Michael"
=> "Michael"
>> user["last_name"] = "Hartl" # Key "last_name", value "Hartl"
=> "Hartl"
>> user["first_name"] # Element access is like arrays.
=> "Michael"
>> user # A literal representation of the hash
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}
Hashes are indicated with curly braces containing key-value pairs; a pair of
braces with no key-value pairs—i.e., {}—is an empty hash. It’s important to
note that the curly braces for hashes have nothing to do with the curly braces
for blocks. (Yes, this can be confusing.) Although hashes resemble arrays,
208 CHAPTER 4. RAILS-FLAVORED RUBY
one important difference is that hashes don’t generally guarantee keeping their
elements in a particular order.
14
If order matters, use an array.
Instead of defining hashes one item at a time using square brackets, it’s
easy to use a literal representation with keys and values separated by =>, called
a “hashrocket”:
>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}
Here I’ve used the usual Ruby convention of putting an extra space at the two
ends of the hash—a convention ignored by the console output. (Don’t ask me
why the spaces are conventional; probably some early influential Ruby pro-
grammer liked the look of the extra spaces, and the convention stuck.)
So far we’ve used strings as hash keys, but in Rails it is much more common
to use symbols instead. Symbols look kind of like strings, but prefixed with a
colon instead of surrounded by quotes. For example, :name is a symbol. You
can think of symbols as basically strings without all the extra baggage:
15
>> "name".split('')
=> ["n", "a", "m", "e"]
>> :name.split('')
NoMethodError: undefined method `split' for :name:Symbol
>> "foobar".reverse
=> "raboof"
>> :foobar.reverse
NoMethodError: undefined method `reverse' for :foobar:Symbol
Symbols are a special Ruby data type shared with very few other languages, so
they may seem weird at first, but Rails uses them a lot, so you’ll get used to
them fast. Unlike strings, not all characters are valid:
14
Ruby versions 1.9 and later actually guarantee that hashes keep their elements in the same order entered, but
it would be unwise ever to count on a particular ordering.
15
As a result of having less baggage, symbols are easier to compare to each other; strings need to be compared
character by character, while symbols can be compared all in one go. This makes them ideal for use as hash keys.
4.3. OTHER DATA STRUCTURES 209
>> :foo-bar
NameError: undefined local variable or method `bar' for main:Object
>> :2foo
SyntaxError
As long as you start your symbols with a letter and stick to normal word char-
acters, you should be fine.
In terms of symbols as hash keys, we can define a user hash as follows:
>> user = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> user[:name] # Access the value corresponding to :name.
=> "Michael Hartl"
>> user[:password] # Access the value of an undefined key.
=> nil
We see here from the last example that the hash value for an undefined key is
simply nil.
Because it’s so common for hashes to use symbols as keys, as of version
1.9 Ruby supports a new syntax just for this special case:
>> h1 = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> h2 = { name: "Michael Hartl", email: "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> h1 == h2
=> true
The second syntax replaces the symbol/hashrocket combination with the name
of the key followed by a colon and a value:
{ name: "Michael Hartl", email: "michael@example.com" }
This construction more closely follows the hash notation in other languages
(such as JavaScript) and enjoys growing popularity in the Rails community.
Because both hash syntaxes are still in common use, it’s essential to be able to
210 CHAPTER 4. RAILS-FLAVORED RUBY
recognize both of them. Unfortunately, this can be confusing, especially since
:name is valid on its own (as a standalone symbol) but name: has no meaning
by itself. The bottom line is that :name => and name: are effectively the same
only inside literal hashes, so that
{ :name => "Michael Hartl" }
and
{ name: "Michael Hartl" }
are equivalent, but otherwise you need to use :name (with the colon coming
first) to denote a symbol.
Hash values can be virtually anything, even other hashes, as seen in List-
ing 4.13.
Listing 4.13: Nested hashes.
>> params = {} # Define a hash called 'params' (short for 'parameters').
=> {}
>> params[:user] = { name: "Michael Hartl", email: "mhartl@example.com" }
=> {:name=>"Michael Hartl", :email=>"mhartl@example.com"}
>> params
=> {:user=>{:name=>"Michael Hartl", :email=>"mhartl@example.com"}}
>> params[:user][:email]
=> "mhartl@example.com"
These sorts of hashes-of-hashes, or nested hashes, are heavily used by Rails, as
we’ll see starting in Section 7.3.
As with arrays and ranges, hashes respond to the each method. For ex-
ample, consider a hash named flash with keys for two conditions, :success
and :danger:
4.3. OTHER DATA STRUCTURES 211
>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}
>> flash.each do |key, value|
?> puts "Key #{key.inspect} has value #{value.inspect}"
>> end
Key :success has value "It worked!"
Key :danger has value "It failed."
Note that, while the each method for arrays takes a block with only one vari-
able, each for hashes takes two, a key and a value. Thus, the each method for
a hash iterates through the hash one key-value pair at a time.
The last example uses the useful inspect method, which returns a string
with a literal representation of the object it’s called on:
>> puts (1..5).to_a # Put an array as a string.
1
2
3
4
5
>> puts (1..5).to_a.inspect # Put a literal array.
[1, 2, 3, 4, 5]
>> puts :name, :name.inspect
name
:name
>> puts "It worked!", "It worked!".inspect
It worked!
"It worked!"
By the way, using inspect to print an object is common enough that there’s a
shortcut for it, the p function:
16
>> p :name # Same output as 'puts :name.inspect'
:name
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
16
There’s actually a subtle difference, which is that p returns the object being printed while puts always returns
nil. (Thanks to reader Katarzyna Siwek for pointing this out.)
212 CHAPTER 4. RAILS-FLAVORED RUBY
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Define a hash with the keys 'one', 'two', and 'three', and the val-
ues 'uno', 'dos', and 'tres'. Iterate over the hash, and for each
key/value pair print out "'#{key}' in Spanish is '#{value}'".
2. Create three hashes called person1, person2, and person3, with first
and last names under the keys :first and :last. Then create a
params hash so that params[:father] is person1,
params[:mother] is person2, and params[:child] is person3.
Verify that, for example, params[:father][:first] has the right
value.
3. Define a hash with symbol keys corresponding to name, email, and a
“password digest”, and values equal to your name, your email address,
and a random string of 16 lower-case letters.
4. Find an online version of the Ruby API and read about the Hash method
merge. What is the value of the following expression?
{ "a" => 100, "b" => 200 }.merge({ "b" => 300 })
4.3.4 CSS revisited
It’s time now to revisit the line from Listing 4.1 used in the layout to include
the Cascading Style Sheets:
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
We are now nearly in a position to understand this. As mentioned briefly in
Section 4.1, Rails defines a special function to include stylesheets, and
4.3. OTHER DATA STRUCTURES 213
stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload'
is a call to this function. But there are several mysteries. First, where are the
parentheses? In Ruby, they are optional, so these two are equivalent:
# Parentheses on function calls are optional.
# This:
stylesheet_link_tag('application', media: 'all',
'data-turbolinks-track': 'reload')
# is the same as this:
stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload'
Second, the media argument sure looks like a hash, but where are the curly
braces? When hashes are the last argument in a function call, the curly braces
are optional, so these two are equivalent:
# Curly braces on final hash arguments are optional.
# This:
stylesheet_link_tag 'application', { media: 'all',
'data-turbolinks-track': 'reload' }
# is the same as this:
stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload'
Finally, why does Ruby correctly interpret the lines
stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload'
even with a line break between the final elements? The answer is that Ruby
doesn’t distinguish between newlines and other whitespace in this context.
17
17
A newline is what comes at the end of a line, thereby starting a new line. As noted in Section 4.2.1, it is
typically represented by the character \n.
214 CHAPTER 4. RAILS-FLAVORED RUBY
The reason I chose to break the code into pieces is that I prefer to keep lines of
source code under 80 characters for legibility.
18
So, we see now that the line
stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload'
calls the stylesheet_link_tag function with two arguments: a string, in-
dicating the path to the stylesheet, and a hash with two elements, indicating the
media type and telling Rails to use the turbolinks feature added in Rails 4.0.
Because of the <%= ... %> brackets, the results are inserted into the template
by ERb, and if you view the source of the page in your browser you should
see the HTML needed to include a stylesheet (Listing 4.14). (The extra stuff
in Listing 4.14, like ?body=1 and the long string of hexadecimal digits are,
inserted by Rails to ensure that browsers reload the CSS when it changes on
the server. Because the hex string is by design unique, your exact version of
Listing 4.14 will differ.)
Listing 4.14: The HTML source produced by the CSS includes.
<link rel="stylesheet" media="all" href="/assets/application.self-
f0d704deea029cf000697e2c0181ec173a1b474645466ed843eb5ee7bb215794.css?body=1"
data-turbolinks-track="reload" />
4.4 Ruby classes
We’ve said before that everything in Ruby is an object, and in this section we’ll
finally get to define some of our own. Ruby, like many object-oriented lan-
guages, uses classes to organize methods; these classes are then instantiated to
18
Constantly having to check the column number is rather inconvenient, so many text editors have a visual aid
to help you. For example, if you take a look back at Figure 1.12, you may be able to make out the small vertical
line on the right side of the screen, which is designed to help keep code under 80 characters. (It’s very subtle, so
you may not be able to see it in the screenshot.) The cloud IDE (Section 1.1.1) includes such a line by default. In
Sublime Text, you can use View > Ruler > 78 or View > Ruler > 80.
4.4. RUBY CLASSES 215
create objects. If you’re new to object-oriented programming, this may sound
like gibberish, so let’s look at some concrete examples.
4.4.1 Constructors
We’ve seen lots of examples of using classes to instantiate objects, but we have
yet to do so explicitly. For example, we instantiated a string using the double
quote characters, which is a literal constructor for strings:
>> s = "foobar" # A literal constructor for strings using double quotes
=> "foobar"
>> s.class
=> String
We see here that strings respond to the method class, and simply return the
class they belong to.
Instead of using a literal constructor, we can use the equivalent named con-
structor, which involves calling the new method on the class name:
19
>> s = String.new("foobar") # A named constructor for a string
=> "foobar"
>> s.class
=> String
>> s == "foobar"
=> true
This is equivalent to the literal constructor, but it’s more explicit about what
we’re doing.
Arrays work the same way as strings:
>> a = Array.new([1, 3, 2])
=> [1, 3, 2]
19
These results will vary based on the version of Ruby you are using. This example assumes you are using
Ruby 1.9.3 or later.
216 CHAPTER 4. RAILS-FLAVORED RUBY
Hashes, in contrast, are different. While the array constructor Array.new takes
an initial value for the array, Hash.new takes a default value for the hash, which
is the value of the hash for a nonexistent key:
>> h = Hash.new
=> {}
>> h[:foo] # Try to access the value for the nonexistent key :foo.
=> nil
>> h = Hash.new(0) # Arrange for nonexistent keys to return 0 instead of nil.
=> {}
>> h[:foo]
=> 0
When a method gets called on the class itself, as in the case of new, it’s
called a class method. The result of calling new on a class is an object of that
class, also called an instance of the class. A method called on an instance, such
as length, is called an instance method.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. What is the literal constructor for the range of integers from 1 to 10?
2. What is the constructor using the Range class and the new method? Hint:
new takes two arguments in this context.
3. Confirm using the == operator that the literal and named constructors
from the previous two exercises are identical.
4.4.2 Class inheritance
When learning about classes, it’s useful to nd out the class hierarchy using the
superclass method:
4.4. RUBY CLASSES 217
Object
String
BasicObject
Figure 4.1: The inheritance hierarchy for the String class.
>> s = String.new("foobar")
=> "foobar"
>> s.class # Find the class of s.
=> String
>> s.class.superclass # Find the superclass of String.
=> Object
>> s.class.superclass.superclass # Ruby has a BasicObject base class as of 1.9
=> BasicObject
>> s.class.superclass.superclass.superclass
=> nil
A diagram of this inheritance hierarchy appears in Figure 4.1. We see here that
the superclass of String is Object and the superclass of Object is Basic-
Object, but BasicObject has no superclass. This pattern is true of every
Ruby object: trace back the class hierarchy far enough and every class in Ruby
ultimately inherits from BasicObject, which has no superclass itself. This is
the technical meaning of everything in Ruby is an object”.
To understand classes a little more deeply, there’s no substitute for making
one of our own. Let’s make a Word class with a palindrome? method that
returns true if the word is the same spelled forward and backward:
218 CHAPTER 4. RAILS-FLAVORED RUBY
>> class Word
>> def palindrome?(string)
>> string == string.reverse
>> end
>> end
=> :palindrome?
We can use it as follows:
>> w = Word.new # Make a new Word object.
=> #<Word:0x22d0b20>
>> w.palindrome?("foobar")
=> false
>> w.palindrome?("level")
=> true
If this example strikes you as a bit contrived, good—this is by design. It’s
odd to create a new class just to create a method that takes a string as an argu-
ment. Since a word is a string, it’s more natural to have our Word class inherit
from String, as seen in Listing 4.15. (You should exit the console and re-enter
it to clear out the old definition of Word.)
Listing 4.15: Defining a Word class in the console.
>> class Word < String # Word inherits from String.
>> # Returns true if the string is its own reverse.
>> def palindrome?
>> self == self.reverse # self is the string itself.
>> end
>> end
=> nil
Here Word < String is the Ruby syntax for inheritance (discussed briefly in
Section 3.2), which ensures that, in addition to the new palindrome? method,
words also have all the same methods as strings:
>> s = Word.new("level") # Make a new Word, initialized with "level".
=> "level"
>> s.palindrome? # Words have the palindrome? method.
4.4. RUBY CLASSES 219
=> true
>> s.length # Words also inherit all the normal string methods.
=> 5
Since the Word class inherits from String, we can use the console to see the
class hierarchy explicitly:
>> s.class
=> Word
>> s.class.superclass
=> String
>> s.class.superclass.superclass
=> Object
This hierarchy is illustrated in Figure 4.2.
In Listing 4.15, note that checking that the word is its own reverse involves
accessing the word inside the Word class. Ruby allows us to do this using the
self keyword: inside the Word class, self is the object itself, which means
we can use
self == self.reverse
to check if the word is a palindrome. In fact, inside the String class the use
of self. is optional on a method or attribute (unless we’re making an assign-
ment), so
self == reverse
would work as well.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
220 CHAPTER 4. RAILS-FLAVORED RUBY
Object
Word
String
BasicObject
Figure 4.2: The inheritance hierarchy for the (non-built-in) Word class from
Listing 4.15.
4.4. RUBY CLASSES 221
1. What is the class hierarchy for a range? For a hash? For a symbol?
2. Confirm that the method shown in Listing 4.15 works even if we replace
self.reverse with just reverse.
4.4.3 Modifying built-in classes
While inheritance is a powerful idea, in the case of palindromes it might be even
more natural to add the palindrome? method to the String class itself, so
that (among other things) we can call palindrome? on a string literal, which
we currently can’t do:
>> "level".palindrome?
NoMethodError: undefined method `palindrome?' for "level":String
Amazingly, Ruby lets you do just this; Ruby classes can be opened and modi-
fied, allowing ordinary mortals such as ourselves to add methods to them:
>> class String
>> # Returns true if the string is its own reverse.
>> def palindrome?
>> self == self.reverse
>> end
>> end
=> nil
>> "deified".palindrome?
=> true
(I don’t know which is cooler: that Ruby lets you add methods to built-in
classes, or that "deified" is a palindrome.)
Modifying built-in classes is a powerful technique, but with great power
comes great responsibility, and it’s considered bad form to add methods to built-
in classes without having a really good reason for doing so. Rails does have
some good reasons; for example, in web applications we often want to prevent
variables from being blank—e.g., a users name should be something other than
spaces and other whitespace—so Rails adds a blank? method to Ruby. Since
222 CHAPTER 4. RAILS-FLAVORED RUBY
the Rails console automatically includes the Rails extensions, we can see an
example here (this won’t work in plain irb):
>> "".blank?
=> true
>> " ".empty?
=> false
>> " ".blank?
=> true
>> nil.blank?
=> true
We see that a string of spaces is not empty, but it is blank. Note also that nil is
blank; since nil isn’t a string, this is a hint that Rails actually adds blank?
to Strings base class, which (as we saw at the beginning of this section)
is Object itself. We’ll see some other examples of Rails additions to Ruby
classes in Section 9.1.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Verify that “racecar” is a palindrome and “onomatopoeia is not. What
about the name of the South Indian language “Malayalam”? Hint: Down-
case it first.
2. Using Listing 4.16 as a guide, add a shuffle method to the String
class. Hint: Refer to Listing 4.12.
3. Verify that Listing 4.16 works even if you remove self..
Listing 4.16: Skeleton for a shuffle method attached to the String class.
4.4. RUBY CLASSES 223
>> class String
>> def shuffle
>> self.?('').?.?
>> end
>> end
>> "foobar".shuffle
=> "borafo"
4.4.4 A controller class
All this talk about classes and inheritance may have triggered a flash of recog-
nition, because we have seen both before, in the Static Pages controller (List-
ing 3.22):
class StaticPagesController < ApplicationController
def home
end
def help
end
def about
end
end
You’re now in a position to appreciate, at least vaguely, what this code means:
StaticPagesController is a class that inherits from ApplicationCon-
troller, and comes equipped with home, help, and about methods. Since
each Rails console session loads the local Rails environment, we can even create
a controller explicitly and examine its class hierarchy:
20
>> controller = StaticPagesController.new
=> #<StaticPagesController:0x22855d0>
>> controller.class
=> StaticPagesController
20
You don’t have to know what each class in this hierarchy does. I don’t know what they all do, and I’ve been
programming in Ruby on Rails since 2005. This means either that (a) I’m grossly incompetent or (b) you can be
a skilled Rails developer without knowing all its innards. I hope for both our sakes that it’s the latter.
224 CHAPTER 4. RAILS-FLAVORED RUBY
>> controller.class.superclass
=> ApplicationController
>> controller.class.superclass.superclass
=> ActionController::Base
>> controller.class.superclass.superclass.superclass
=> ActionController::Metal
>> controller.class.superclass.superclass.superclass.superclass
=> AbstractController::Base
>> controller.class.superclass.superclass.superclass.superclass.superclass
=> Object
A diagram of this hierarchy appears in Figure 4.3.
We can even call the controller actions inside the console, which are just
methods:
>> controller.home
=> nil
Here the return value is nil because the home action is blank.
But wait—actions don’t have return values, at least not ones that matter.
The point of the home action, as we saw in Chapter 3, is to render a web page,
not to return a value. And I sure don’t remember ever calling StaticPages-
Controller.new anywhere. What’s going on?
What’s going on is that Rails is written in Ruby, but Rails isn’t Ruby. Some
Rails classes are used like ordinary Ruby objects, but some are just grist for
Rails’ magic mill. Rails is sui generis, and should be studied and understood
separately from Ruby.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. By running the Rails console in the toy app’s directory from Chapter 2,
confirm that you can create a user object using User.new.
2. Determine the class hierarchy of the user object.
4.4. RUBY CLASSES 225
ActionController::Base
StaticPagesController
ApplicationController
Object
ActionController::Metal
AbstractController::Base
BasicObject
Figure 4.3: The inheritance hierarchy for the Static Pages.
226 CHAPTER 4. RAILS-FLAVORED RUBY
4.4.5 A user class
We end our tour of Ruby with a complete class of our own, a User class that
anticipates the User model coming up in Chapter 6.
So far we’ve entered class definitions at the console, but this quickly be-
comes tiresome; instead, create the file example_user.rb in your application
root directory and fill it with the contents of Listing 4.17.
Listing 4.17: Code for an example user.
example_user.rb
class User
attr_accessor :name, :email
def initialize(attributes = {})
@name = attributes[:name]
@email = attributes[:email]
end
def formatted_email
"#{@name} <#{@email}>"
end
end
There’s quite a bit going on here, so let’s take it step by step. The first line,
attr_accessor :name, :email
creates attribute accessors corresponding to a users name and email address.
This creates “getter” and “setter” methods that allow us to retrieve (get) and as-
sign (set) @name and @email instance variables, which were mentioned briefly
in Section 2.2.2 and Section 3.4.2. In Rails, the principal importance of instance
variables is that they are automatically available in the views, but in general they
are used for variables that need to be available throughout a Ruby class. (We’ll
have more to say about this in a moment.) Instance variables always begin with
an @ sign, and are nil when undefined.
The rst method, initialize, is special in Ruby: it’s the method called
when we execute User.new. This particular initialize takes one argument,
attributes:
4.4. RUBY CLASSES 227
def initialize(attributes = {})
@name = attributes[:name]
@email = attributes[:email]
end
Here the attributes variable has a default value equal to the empty hash,
so that we can define a user with no name or email address. (Recall from
Section 4.3.3 that hashes return nil for nonexistent keys, so attributes-
[:name] will be nil if there is no :name key, and similarly for attributes-
[:email].)
Finally, our class defines a method called formatted_email that uses the
values of the assigned @name and @email variables to build up a nicely format-
ted version of the users email address using string interpolation (Section 4.2.1):
def formatted_email
"#{@name} <#{@email}>"
end
Because @name and @email are both instance variables (as indicated with the @
sign), they are automatically available in the formatted_email method.
Let’s fire up the console, require the example user code, and take our User
class out for a spin:
>> require './example_user' # This is how you load the example_user code.
=> true
>> example = User.new
=> #<User:0x224ceec @email=nil, @name=nil>
>> example.name # nil since attributes[:name] is nil
=> nil
>> example.name = "Example User" # Assign a non-nil name
=> "Example User"
>> example.email = "user@example.com" # and a non-nil email address
=> "user@example.com"
>> example.formatted_email
=> "Example User <user@example.com>"
Here the '.' is Unix for “current directory”, and './example_user' tells
Ruby to look for an example user file relative to that location. The subsequent
228 CHAPTER 4. RAILS-FLAVORED RUBY
code creates an empty example user and then fills in the name and email ad-
dress by assigning directly to the corresponding attributes (assignments made
possible by the attr_accessor line in Listing 4.17). When we write
example.name = "Example User"
Ruby is setting the @name variable to "Example User" (and similarly for the
email attribute), which we then use in the formatted_email method.
Recalling from Section 4.3.4 we can omit the curly braces for final hash
arguments, we can create another user by passing a hash to the initialize
method to create a user with pre-defined attributes:
>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User:0x225167c @email="mhartl@example.com", @name="Michael Hartl">
>> user.formatted_email
=> "Michael Hartl <mhartl@example.com>"
We will see starting in Chapter 7 that initializing objects using a hash argument,
a technique known as mass assignment, is common in Rails applications.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. In the example User class, change from name to separate first and last
name attributes, and then add a method called full_name that returns
the first and last names separated by a space. Use it to replace the use of
name in the formatted email method.
2. Add a method called alphabetical_name that returns the last name
and first name separated by comma-space.
3. Verify that full_name.split is the same as alphabetical_name.-
split(', ').reverse.
4.5. CONCLUSION 229
4.5 Conclusion
This concludes our overview of the Ruby language. In Chapter 5, we’ll start
putting it to good use in developing the sample application.
We won’t be using the example_user.rb file from Section 4.4.5, so I
suggest removing it:
$ rm example_user.rb
Then commit the other changes to the main source code repository and merge
into the master branch, push up to GitHub, and deploy to Heroku:
$ git commit -am "Add a full_title helper"
$ git checkout master
$ git merge rails-flavored-ruby
As a reality check, it’s a good practice to run the test suite before pushing or
deploying:
$ rails test
Then push up to GitHub:
$ git push
Finally, deploy to Heroku:
$ git push heroku
230 CHAPTER 4. RAILS-FLAVORED RUBY
4.5.1 What we learned in this chapter
Ruby has a large number of methods for manipulating strings of charac-
ters.
Everything in Ruby is an object.
Ruby supports method definition via the def keyword.
Ruby supports class definition via the class keyword.
Rails views can contain static HTML or embedded Ruby (ERb).
Built-in Ruby data structures include arrays, ranges, and hashes.
Ruby blocks are a flexible construct that (among other things) allow nat-
ural iteration over enumerable data structures.
Symbols are labels, like strings without any additional structure.
Ruby supports object inheritance.
It is possible to open up and modify built-in Ruby classes.
The word “deified” is a palindrome.
Chapter 5
Filling in the layout
In the process of taking a brief tour of Ruby in Chapter 4, we learned about
including the application stylesheet into the sample application (Section 4.1),
but (as noted in
Section 4.3.4) the stylesheet doesn’t yet contain any CSS. In
this chapter, we’ll start filling in the custom stylesheet by incorporating a CSS
framework into our application, and then we’ll add some custom styles of our
own.
1
We’ll also start filling in the layout with links to the pages (such as Home
and About) that we’ve created so far (Section 5.1). Along the way, we’ll learn
about partials, Rails routes, and the asset pipeline, including an introduction
to Sass (Section 5.2). We’ll end by taking a first important step toward letting
users sign up to our site (Section 5.4).
Most of the changes in this chapter involve adding and editing markup in
the sample application’s site layout, which (based on the guidelines in Box 3.3)
is exactly the kind of work that we wouldn’t ordinarily test-drive, or even test
at all. As a result, we’ll spend most of our time in our text editor and browser,
using TDD only to add a Contact page (Section 5.3.1). We will add an important
new test, though, writing our first integration test to check that the links on the
final layout are correct (Section 5.3.4).
1
Thanks to reader Colm Tuite for his excellent work in helping to convert the sample application over to the
Bootstrap CSS framework.
231
232 CHAPTER 5. FILLING IN THE LAYOUT
5.1 Adding some structure
The Ruby on Rails Tutorial is a book on web development, not web design,
but it would be depressing to work on an application that looks like complete
garbage, so in this section we’ll add some structure to the layout and give it some
minimal styling with CSS. In addition to using some custom CSS rules, we’ll
make use of Bootstrap, an open-source web design framework from Twitter.
2
(This design will initially be optimized for desktop/laptop computers, but we’ll
include some additional some rules for mobile devices in Section 8.2.3.) We’ll
also give our code some styling, so to speak, using partials to tidy up the layout
once it gets a little cluttered.
When building web applications, it is often useful to get a high-level over-
view of the user interface as early as possible. Throughout the rest of this book,
we will thus often consider mockups (in a web context often called wireframes),
which are rough sketches of what the eventual application will look like.
3
In
this chapter, we will principally be developing the static pages introduced in
Section 3.2, including a site logo, a navigation header, and a site footer. A
mockup for the most important of these pages, the Home page, appears in Fig-
ure 5.1. You can see the final result in Figure 5.9. You’ll note that it differs in
some details—for example, we’ll end up adding a Rails logo on the page—but
that’s fine, since a mockup need not be exact.
As usual, if you’re using Git for version control, now would be a good time
to make a new branch:
$ git checkout -b filling-in-layout
2
Although more recent versions of Bootstrap are now available, this tutorial standardizes on Bootstrap 3 in
order to retain compatibility with the design and HTML structure from previous editions. The latest version of
Bootstrap is similar, so the skills you learn here are highly transferable.
3
The mockups in the Ruby on Rails Tutorial are made with an excellent online mockup application called
Mockingbird.
5.1. ADDING SOME STRUCTURE 233
Figure 5.1: A mockup of the sample application’s Home page.
234 CHAPTER 5. FILLING IN THE LAYOUT
5.1.1 Site navigation
As a first step toward adding links and styles to the sample application, we’ll up-
date the site layout file application.html.erb (last seen in Listing 4.3) with
additional HTML structure. This includes some additional divisions, some CSS
classes, and the start of our site navigation. The full file is in Listing 5.1; ex-
planations for the various pieces follow immediately thereafter. If you’d rather
not delay gratification, you can see the results in Figure 5.2. (Note: it’s not (yet)
very gratifying.)
Listing 5.1: The site layout with added structure.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<meta charset="utf-8">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
</head>
<body>
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
<div class="container">
<%= yield %>
</div>
</body>
</html>
5.1. ADDING SOME STRUCTURE 235
Let’s look at the new elements in Listing 5.1 from top to bottom. As alluded
to briefly in Section 3.4.1, Rails uses HTML5 by default (as indicated by the
doctype <!DOCTYPE html>) which at this point most browsers support, but we
can make our site more accessible to older browsers by adding some JavaScript
code, known as an HTML5 shim (or shiv)”:
4
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
The somewhat odd syntax
<!--[if lt IE 9]>
includes the enclosed line only if the version of Microsoft Internet Explorer (IE)
is less than 9 (
if lt IE 9). The weird [if lt IE 9] syntax is
not
part
of Rails; it’s actually a conditional comment supported by Internet Explorer
browsers for just this sort of situation. It’s a good thing, too, because it means we
can include the HTML5 shim only for IE browsers less than version 9, leaving
other browsers such as Firefox, Chrome, and Safari unaffected.
The next section includes a header for the site’s (plain-text) logo, a couple
of divisions (using the div tag), and a list of elements with navigation links:
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
4
The words shim and shiv are used interchangably in this context; the former is the proper term, based on the
English word whose meaning is “a washer or thin strip of material used to align parts, make them fit, or reduce
wear”, while the latter (meaning “a knife or razor used as a weapon”) is apparently a play on the name of the
shim’s original author, Sjoerd Visscher.
236 CHAPTER 5. FILLING IN THE LAYOUT
</nav>
</div>
</header>
Here the header tag indicates elements that should go at the top of the page.
We’ve given the header tag three CSS classes,
5
called navbar, navbar-
fixed-top, and navbar-inverse, separated by spaces:
<header class="navbar navbar-fixed-top navbar-inverse">
All HTML elements can be assigned both classes and ids;
6
these are merely
labels, and are useful for styling with CSS (Section 5.1.2). The main difference
between classes and ids is that classes can be used multiple times on a page,
but ids can be used only once. In the present case, all the navbar classes have
special meaning to the Bootstrap framework, which we’ll install and use in
Section 5.1.2.
Inside the header tag, we see a div tag:
<div class="container">
The div tag is a generic division; it doesn’t do anything apart from divide the
document into distinct parts. In older-style HTML, div tags are used for nearly
all site divisions, but HTML5 adds the header, nav, and section elements
for divisions common to many applications. In this case, the div has a CSS
class as well (container). As with the header tag’s classes, this class has
special meaning to Bootstrap.
After the div, we encounter some embedded Ruby:
5
These are completely unrelated to Ruby classes.
6
Short for “identification” and pronounced as the separate letters “I D”. The usual convention in English is to
use all-caps (“ID”), reserving id” for a term in Freudian psychoanalysis. Because HTML is usually typed in all
lower-case letters, though, it’s more common in this context to write “id” instead.
5.1. ADDING SOME STRUCTURE 237
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
This uses the Rails helper link_to to create links (which we created directly
with the anchor tag a in Section 3.2.2); the first argument to link_to is the link
text, while the second is the URL. We’ll fill in the URLs with named routes in
Section 5.3.3, but for now we use the stub URL '#' commonly used in web
design (i.e., '#' is just a “stub”, or placeholder, for the real URL). The third
argument is an options hash, in this case adding the CSS id logo to the sample
app link. (The other three links have no options hash, which is fine since it’s
optional.) Rails helpers often take options hashes in this way, giving us the
flexibility to add arbitrary HTML options without ever leaving Rails.
The second element inside the divs is a list of navigation links, made using
the unordered list tag ul, together with the list item tag li:
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
The <nav> tag, though formally unnecessary here, is used to more clearly com-
municate the purpose of the navigation links. Meanwhile, the nav,
navbar-nav, and navbar-right classes on the ul tag have special mean-
ing to Bootstrap and will be styled automatically when we include the Boot-
strap CSS in Section 5.1.2. As you can verify by inspecting the navigation in
your browser,
7
once Rails has processed the layout and evaluated the embedded
7
All modern browsers have the capability to inspect the HTML source of a page. If you’ve never used a web
inspector before, do a web search for something like “web inspector <name of browser>” to learn more.
238 CHAPTER 5. FILLING IN THE LAYOUT
Ruby the list looks like this:
8
<nav>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Home</a></li>
<li><a href="#">Help</a></li>
<li><a href="#">Log in</a></li>
</ul>
</nav>
This is the text that will be returned to the browser.
The final part of the layout is a div for the main content:
<div class="container">
<%= yield %>
</div>
As before, the container class has special meaning to Bootstrap. As we
learned in Section 3.4.3, the yield method inserts the contents of each page
into the site layout.
Apart from the site footer, which we’ll add in Section 5.1.3, our layout is
now complete, and we can look at the results by visiting the Home page. To
take advantage of the upcoming style elements, we’ll add some extra elements
to the home.html.erb view (Listing 5.2).
Listing 5.2: The Home page with a link to the signup page.
app/views/static_pages/home.html.erb
<div class="center jumbotron">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page for the
<a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</h2>
8
The spacing might look slightly different, which is fine because (as noted in Section 3.4.1) HTML is insensitive
to whitespace.
5.1. ADDING SOME STRUCTURE 239
<%= link_to "Sign up now!", '#', class: "btn btn-lg btn-primary" %>
</div>
<%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200"),
"https://rubyonrails.org/" %>
In preparation for adding users to our site in Chapter 7, the first link_to creates
a stub link of the form
<a href="#" class="btn btn-lg btn-primary">Sign up now!</a>
In the div tag, the jumbotron CSS class has a special meaning to Bootstrap,
as do the btn, btn-lg, and btn-primary classes in the signup button.
The second link_to shows off the image_tag helper, which takes as ar-
guments the path to an image and an optional options hash, in this case setting
the alt and width attributes of the image tag using symbols. For this to work,
there needs to be an image called rails.svg, which you should download
from the Learn Enough website at https://cdn.learnenough.com/rails.svg and
place in the app/assets/images/ directory.
If you’re using the cloud IDE or another Unix-like system, you can accom-
plish this with the curl utility, as shown in Listing 5.3.
9
Listing 5.3: Downloading an image.
$ curl -o app/assets/images/rails.svg -OL https://cdn.learnenough.com/rails.svg
Because we used the image_tag helper in Listing 5.2, Rails will automati-
cally find any images in the app/assets/images/ directory using the asset
pipeline (Section 5.2).
Now we’re finally ready to see the fruits of our labors. You may have to
restart the Rails server to see the changes (Box 1.2), and the results should
appear as shown in Figure 5.2.
9
See Learn Enough Command Line to Be Dangerous for more information about curl.
240 CHAPTER 5. FILLING IN THE LAYOUT
Figure 5.2: The Home page with no custom CSS.
5.1. ADDING SOME STRUCTURE 241
To make the effects of image_tag clearer, let’s look at the HTML it pro-
duces by inspecting the image in our browser:
10
<img alt="Rails logo" width="200px" src="/assets/rails-<long string>.svg">
Here the <long string> is a random value added by Rails to ensure that the
filename is unique, which causes browsers to load images properly when they
have been updated (instead of retrieving them from the browser cache). Note
that the src attribute doesn’t include images, instead using an assets direc-
tory common to all assets (images, JavaScript, CSS, etc.). On the server, Rails
associates images in the assets directory with the proper app/assets/-
images directory, but as far as the browser is concerned all the assets look like
they are in the same directory, which allows them to be served faster. Mean-
while, the alt attribute is what will be displayed if the page is accessed by a
program that can’t display images (such as screen readers for the visually im-
paired).
As for the result shown in Figure 5.2, it might look a little underwhelming.
Happily, though, we’ve done a good job of giving our HTML elements sensible
classes, which puts us in a great position to add style to the site with CSS.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. It’s well-known that no web page is complete without a cat image. Using
the command in Listing 5.4, arrange to download the kitten pic shown in
Figure 5.3.
11
10
You might notice that the img tag, rather than looking like <img>...</img>, instead looks like
<img ... />. Tags that follow this form are known as self-closing tags.
11
Image retrieved from https://www.flickr.com/photos/deborah_s_perspective/14144861329 on 2016-01-09.
Copyright © 2009 by Deborah and used unaltered under the terms of the Creative Commons Attribution 2.0
Generic license.
242 CHAPTER 5. FILLING IN THE LAYOUT
Figure 5.3: An obligatory kitten pic.
2. Using the mv command, move kitten.jpg to the correct asset directory
for images (Section 5.2.1).
3. Using image_tag, add kitten.jpg to the Home page, as shown in
Figure 5.4.
Listing 5.4: Downloading a cat picture from the Internet.
$ curl -OL https://cdn.learnenough.com/kitten.jpg
5.1. ADDING SOME STRUCTURE 243
Figure 5.4: The result of adding a kitten image to the Home page.
244 CHAPTER 5. FILLING IN THE LAYOUT
5.1.2 Bootstrap and custom CSS
In Section 5.1.1, we associated many of the HTML elements with CSS classes,
which gives us considerable flexibility in constructing a layout based on CSS.
As noted in Section 5.1.1, many of these classes are specific to Bootstrap, a CSS
framework that makes it easy to add nice web design and user interface elements
to an HTML5 application. In this section, we’ll combine Bootstrap with some
custom CSS rules to start adding some style to the sample application. Its
worth noting that using Bootstrap automatically makes our application’s design
responsive, ensuring that it looks sensible across a wide range of devices.
Our first step is to add Bootstrap, which in Rails applications can be ac-
complished with the bootstrap-sass gem, as shown in Listing 5.5.
12
The
Bootstrap framework natively uses the Less CSS language for making dynamic
stylesheets, but the Rails asset pipeline supports the (very similar) Sass lan-
guage by default (Section 5.2), so bootstrap-sass converts Less to Sass
and makes all the necessary Bootstrap files available to the current application.
Listing 5.5: Adding the
bootstrap-sass
gem to the Gemfile.
source 'https://rubygems.org'
gem 'rails', '6.0.2.1'
gem 'bootstrap-sass', '3.4.1'
gem 'puma', '3.12.2'
.
.
.
To install Bootstrap, we run bundle install as usual:
$ bundle install
Although rails generate automatically creates a separate CSS file for
each controller, it’s surprisingly hard to include them all properly and in the
12
As always, you should use the version numbers listed at gemfiles-6th-ed.railstutorial.org instead of the ones
listed here.
5.1. ADDING SOME STRUCTURE 245
right order, so for simplicity we’ll put all of the CSS needed for this tutorial in
a single file. The first step toward getting custom CSS to work is to create such
a custom CSS file:
$ touch app/assets/stylesheets/custom.scss
(This uses the touch trick from Section 3.3.3 en route, but you can create the
file however you like.) Here both the directory name and filename extension
are important. The directory
app/assets/stylesheets/
is part of the asset pipeline (Section 5.2), and any stylesheets in this directory
will automatically be included as part of the application.css file included
in the site layout. Furthermore, the lename custom.scss includes the .scss
extension, which indicates a “Sassy CSS” file and arranges for the asset pipeline
to process the file using Sass. (We won’t be using Sass until Section 5.2.2, but
it’s needed now for the bootstrap-sass gem to work its magic.)
Inside the file for the custom CSS, we can use the @import function to
include Bootstrap (together with the associated Sprockets utility), as shown in
Listing 5.6.
13
Listing 5.6: Adding Bootstrap CSS.
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";
The two lines in Listing 5.6 include the entire Bootstrap CSS framework. Af-
ter restarting the webserver to incorporate the changes into the development
application (by pressing Ctrl-C and then running rails server as in Sec-
tion 1.2.2), the results appear as in Figure 5.5. The placement of the text isn’t
13
If these steps seem mysterious, take heart: I’m just following the instructions from the bootstrap-sass
README file.
246 CHAPTER 5. FILLING IN THE LAYOUT
Figure 5.5: The sample application with Bootstrap CSS.
good and the logo doesn’t have any style, but the colors and signup button look
promising.
Next we’ll add some CSS that will be used site-wide for styling the layout
and each individual page, as shown in Listing 5.7. The result is shown in Fig-
ure 5.6. (There are quite a few rules in Listing 5.7; to get a sense of what a
CSS rule does, it’s often helpful to comment it out using CSS comments, i.e.,
by putting it inside /* … */, and seeing what changes.)
Listing 5.7: Adding CSS for some universal styling applying to all pages.
app/assets/stylesheets/custom.scss
5.1. ADDING SOME STRUCTURE 247
@import "bootstrap-sprockets";
@import "bootstrap";
/* universal */
body {
padding-top: 60px;
}
section {
overflow: auto;
}
textarea {
resize: vertical;
}
.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}
Note that the CSS in Listing 5.7 has a consistent form. In general, CSS
rules refer either to a class, an id, an HTML tag, or some combination thereof,
followed by a list of styling commands. For example,
body {
padding-top: 60px;
}
puts 60 pixels of padding at the top of the page. Because of the
navbar-fixed-top class in the header tag, Bootstrap fixes the navigation
bar to the top of the page, so the padding serves to separate the main text from
the navigation. (Because the default navbar color changed after Bootstrap 2.0,
we need the navbar-inverse class to make it dark instead of light.) Mean-
while, the CSS in the rule
248 CHAPTER 5. FILLING IN THE LAYOUT
Figure 5.6: Adding some spacing and other universal styling.
5.1. ADDING SOME STRUCTURE 249
.center {
text-align: center;
}
associates the center class with the text-align: center property. In
other words, the dot . in .center indicates that the rule styles a class. (As
we’ll see in Listing 5.9, the pound sign # identifies a rule to style a CSS id.)
This means that elements inside any tag (such as a div) with class center will
be centered on the page. (We saw an example of this class in Listing 5.2.)
Although Bootstrap comes with CSS rules for nice typography, we’ll also
add some custom rules for the appearance of the text on our site, as shown in
Listing 5.8. (Not all of these rules apply to the Home page, but each rule here
will be used at some point in the sample application.) The result of Listing 5.8
is shown in Figure 5.7.
Listing 5.8: Adding CSS for nice typography.
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* typography */
h1, h2, h3, h4, h5, h6 {
line-height: 1;
}
h1 {
font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;
}
h2 {
font-size: 1.2em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: #777;
250 CHAPTER 5. FILLING IN THE LAYOUT
Figure 5.7: Adding some typographic styling.
}
p {
font-size: 1.1em;
line-height: 1.7em;
}
Finally, we’ll add some rules to style the site’s logo, which simply consists
of the text “sample app”. The CSS in Listing 5.9 converts the text to uppercase
and modifies its size, color, and placement. (We’ve used a CSS id because we
expect the site logo to appear on the page only once, but you could use a class
instead.)
5.1. ADDING SOME STRUCTURE 251
Listing 5.9: Adding CSS for the site logo.
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* header */
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
}
#logo:hover {
color: #fff;
text-decoration: none;
}
Here color: #fff changes the color of the logo to white. HTML colors can
be coded with three pairs of base-16 (hexadecimal) numbers, one each for the
primary colors red, green, and blue (in that order). The code #ffffff maxes
out all three colors, yielding pure white, and #fff is a shorthand for the full
#ffffff. The CSS standard also defines a large number of synonyms for com-
mon HTML colors, including white for #fff. The result of the CSS in List-
ing 5.9 is shown in Figure 5.8.
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Using code like that shown in Listing 5.10, comment out the cat image
252 CHAPTER 5. FILLING IN THE LAYOUT
Figure 5.8: The sample app with nicely styled logo.
5.1. ADDING SOME STRUCTURE 253
from Section 5.1.1. Verify using a web inspector that the HTML for the
image no longer appears in the page source.
2. By adding the CSS in Listing 5.11 to custom.scss, hide all images in
the application—currently just the Rails logo on the Home page). Verify
with a web inspector that, although the image doesn’t appear, the HTML
source is still present.
Listing 5.10: Code to comment out embedded Ruby.
<%#= image_tag("kitten.jpg", alt: "Kitten") %>
Listing 5.11: CSS to hide all images.
img {
display: none;
}
5.1.3 Partials
Although the layout in Listing 5.1 serves its purpose, it’s getting a little clut-
tered. The HTML shim takes up three lines and uses weird IE-specific syntax,
so it would be nice to tuck it away somewhere on its own. In addition, the header
HTML forms a logical unit, so it should all be packaged up in one place. The
way to achieve this in Rails is to use a facility called partials. Let’s first take a
look at what the layout looks like after the partials are defined (Listing 5.12).
Listing 5.12: The site layout with partials for the stylesheets and header.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<meta charset="utf-8">
<%= csrf_meta_tags %>
254 CHAPTER 5. FILLING IN THE LAYOUT
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
</div>
</body>
</html>
In Listing 5.12, we’ve replaced the HTML shim stylesheet lines with a sin-
gle call to a Rails helper called render:
<%= render 'layouts/shim' %>
The effect of this line is to look for a file called app/views/layouts/-
_shim.html.erb
, evaluate its contents, and insert the results into the view.
14
(Recall that <%= ... %> is the embedded Ruby syntax needed to evaluate a
Ruby expression and then insert the results into the template.) Note the leading
underscore on the filename _shim.html.erb; this underscore is the universal
convention for naming partials, and among other things makes it possible to
identify all the partials in a directory at a glance.
To get the partial to work, we have to create the corresponding file and fill
it with some content. In the case of the shim partial, this is just the three lines
of shim code from Listing 5.1. The result appears in Listing 5.13.
Listing 5.13: A partial for the HTML shim.
app/views/layouts/_shim.html.erb
14
Many Rails developers use a shared directory for partials shared across different views. I prefer to use the
shared folder for utility partials that are useful on multiple views, while putting partials that are literally on every
page (as part of the site layout) in the layouts directory. (We’ll create the shared directory starting in Chapter 7.)
That seems to me a logical division, but putting them all in the shared folder certainly works fine, too.
5.1. ADDING SOME STRUCTURE 255
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
Similarly, we can move the header material into the partial shown in List-
ing 5.14 and insert it into the layout with another call to render. (As usual
with partials, you will have to create the file by hand using your text editor.)
Listing 5.14: A partial for the site header.
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
Now that we know how to make partials, let’s add a site footer to go along
with the header. By now you can probably guess that we’ll call it _footer.-
html.erb and put it in the layouts directory (Listing 5.15).
15
Listing 5.15: A partial for the site footer.
app/views/layouts/_footer.html.erb
<footer class="footer">
<small>
The <a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a>
by <a href="https://www.michaelhartl.com/">Michael Hartl</a>
</small>
15
You may wonder why we use both the footer tag and .footer class. The answer is that the tag has a clear
meaning to human readers, and the class is used by Bootstrap. Using a div tag in place of footer would work
as well.
256 CHAPTER 5. FILLING IN THE LAYOUT
<nav>
<ul>
<
li><%= link_to "About", '#' %></li>
<li><%= link_to "Contact", '#' %></li>
<li><a href="https://news.railstutorial.org/">News</a></li>
</ul>
</nav>
</footer>
As with the header, in the footer we’ve used link_to for the internal links to
the About and Contact pages and stubbed out the URLs with '#' for now. (As
with header, the footer tag is new in HTML5.)
We can render the footer partial in the layout by following the same pattern
as the stylesheets and header partials (Listing 5.16).
Listing 5.16: The site layout with a footer partial.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<meta charset="utf-8">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
</html>
Next, we’ll add some styling for the footer, as shown in Listing 5.17. The
results appear in Figure 5.9.
5.1. ADDING SOME STRUCTURE 257
Listing 5.17: Adding the CSS for the site footer.
app/assets/stylesheets/custom.scss
.
.
.
/* footer */
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
color: #777;
}
footer a {
color: #555;
}
footer a:hover {
color: #222;
}
footer small {
float: left;
}
footer ul {
float: right;
list-style: none;
}
footer ul li {
float: left;
margin-left: 15px;
}
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Replace the default Rails head with the call to render shown in List-
ing 5.18. Hint: For convenience, cut the default header rather than just
258 CHAPTER 5. FILLING IN THE LAYOUT
Figure 5.9: The Home page with an added footer.
5.2. SASS AND THE ASSET PIPELINE 259
deleting it.
2. Because we haven’t yet created the partial needed by Listing 5.18, the
tests should be red. Confirm that this is the case.
3. Create the necessary partial in the layouts directory, paste in the con-
tents, and verify that the tests are now green again.
Listing 5.18: Replacing the default Rails head with a call to render.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<meta charset="utf-8">
<%= render 'layouts/rails_default' %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
</html>
5.2 Sass and the asset pipeline
One of the most useful features of Rails is the asset pipeline, which significantly
simplifies the production and management of static assets such as CSS and im-
ages. The asset pipline also works well in parallel with Webpack (a JavaScript
asset bundler) and Yarn (a dependency manager mentioned in Section 1.1.2),
both of which are supported by default in Rails. This section first gives a high-
level overview of the asset pipeline, and then shows how to use Sass, a powerful
tool for writing CSS.
260 CHAPTER 5. FILLING IN THE LAYOUT
5.2.1 The asset pipeline
From the perspective of a typical Rails developer, there are three main features
to understand about the asset pipeline: asset directories, manifest files, and pre-
processor engines.
16
Let’s consider each in turn.
Asset directories
The Rails asset pipeline uses three standard directories for static assets, each
with its own purpose:
app/assets: assets specific to the present application
lib/assets: assets for libraries written by your dev team
vendor/assets: assets from third-party vendors (not present by de-
fault)
Each of these directories has a subdirectory for each of two asset classes—
images and Cascading Style Sheets:
$ ls app/assets/
config images stylesheets
At this point, we’re in a position to understand the motivation behind the
location of the custom CSS file in Section 5.1.2: custom.scss is specific to
the sample application, so it goes in app/assets/stylesheets.
Manifest files
Once you’ve placed your assets in their logical locations, you can use manifest
files to tell Rails (via the Sprockets gem) how to combine them to form single
files. (This applies to CSS and JavaScript but not to images.) As an example,
let’s take a look at the default manifest file for app stylesheets (Listing 5.19).
16
The original structure of this section was based on the excellent blog post “The Rails 3 Asset Pipeline in
(about) 5 Minutes” by Michael Erasmus.
5.2. SASS AND THE ASSET PIPELINE 261
Listing 5.19: The manifest file for app-specific CSS.
app/assets/stylesheets/application.css
/*
* This is a manifest file that'll be compiled into application.css, which will
* include all the files listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any
* plugin's vendor/assets/stylesheets directory can be referenced here using a
* relative path.
*
* You're free to add application-wide styles to this file and they'll appear at
* the bottom of the compiled file so the styles you add here take precedence
* over styles defined in any other CSS/SCSS files in this directory. Styles in
* this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/
The key lines here are actually CSS comments, but they are used by Sprockets
to include the proper files:
/*
.
.
.
*= require_tree .
*= require_self
*/
Here
*= require_tree .
ensures that all CSS files in the app/assets/stylesheets directory (includ-
ing the tree subdirectories) are included into the application CSS. The line
262 CHAPTER 5. FILLING IN THE LAYOUT
*= require_self
specifies where in the loading sequence the CSS in application.css itself
gets included.
Rails comes with sensible default manifest files, and in the Rails Tutorial
we won’t need to make any changes, but the Rails Guides entry on the asset
pipeline has more detail if you need it.
Preprocessor engines
After you’ve assembled your assets, Rails prepares them for the site template by
running them through several preprocessing engines and using the manifest files
to combine them for delivery to the browser. We tell Rails which processor to
use using filename extensions; the two most common cases are .scss for Sass
and .erb for embedded Ruby (ERb). We first covered ERb in Section 3.4.3
and cover Sass in Section 5.2.2.
Efficiency in production
One of the best things about the asset pipeline is that it automatically results in
assets that are optimized to be efficient in a production application. Traditional
methods for organizing CSS involves splitting functionality into separate files
and using nice formatting (with lots of indentation). While convenient for the
programmer, this is inefficient in production. In particular, including multiple
full-sized files can significantly slow page-load times, which is one of the most
important factors affecting the quality of the user experience.
With the asset pipeline, we don’t have to choose between speed and con-
venience: we can work with multiple nicely formatted files in development,
and then use the asset pipeline to make efficient files in production. In partic-
ular, the asset pipeline combines all the application stylesheets into one CSS
file (application.css) and then minifies it to remove the unnecessary spac-
ing and indentation that bloats file size. The result is the best of both worlds:
convenience in development and efficiency in production.
5.2. SASS AND THE ASSET PIPELINE 263
5.2.2 Syntactically awesome stylesheets
Sass is a language for writing stylesheets that improves on CSS in many ways.
In this section, we cover two of the most important improvements, nesting and
variables. (A third technique, mixins, is introduced in Section 7.1.1.)
As noted briefly in Section 5.1.2, Sass supports a format called SCSS (in-
dicated with a .scss filename extension), which is a strict superset of CSS
itself; that is, SCSS only adds features to CSS, rather than defining an entirely
new syntax.
17
This means that every valid CSS file is also a valid SCSS file,
which is convenient for projects with existing style rules. In our case, we used
SCSS from the start in order to take advantage of Bootstrap. Since the Rails as-
set pipeline automatically uses Sass to process files with the .scss extension,
the custom.scss file will be run through the Sass preprocessor before being
packaged up for delivery to the browser.
Nesting
A common pattern in stylesheets is having rules that apply to nested elements.
For example, in Listing 5.7 we have rules both for .center and for .center
h1:
.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}
We can replace this in Sass with
.center {
text-align: center;
h1 {
margin-bottom: 10px;
17
Sass also supports an alternate syntax that does define a new language, which is less verbose (and has fewer
curly braces) but is less convenient for existing projects and is harder to learn for those already familiar with CSS.
264 CHAPTER 5. FILLING IN THE LAYOUT
}
}
Here the nested h1 rule automatically inherits the .center context.
There’s a second candidate for nesting that requires a slightly different syn-
tax. In Listing 5.9, we have the code
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
}
#logo:hover {
color: #fff;
text-decoration: none;
}
Here the logo id #logo appears twice, once by itself and once with the hover
attribute (which controls its appearance when the mouse pointer hovers over
the element in question). In order to nest the second rule, we need to reference
the parent element #logo; in SCSS, this is accomplished with the ampersand
character & as follows:
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
&:hover {
color: #fff;
text-decoration: none;
}
}
5.2. SASS AND THE ASSET PIPELINE 265
Sass changes &:hover into #logo:hover as part of converting from SCSS to
CSS.
Both of these nesting techniques apply to the footer CSS in Listing 5.17,
which can be transformed into the following:
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
color: #777;
a {
color: #555;
&:hover {
color: #222;
}
}
small {
float: left;
}
ul {
float: right;
list-style: none;
li {
float: left;
margin-left: 15px;
}
}
}
Converting Listing 5.17 by hand is a good exercise (Section 5.2.2), and you
should verify that the CSS still works properly after the conversion.
Variables
Sass allows us to define variables to eliminate duplication and write more ex-
pressive code. For example, looking at Listing 5.8 and Listing 5.17, we see that
there are repeated references to the same color:
h2 {
.
.
.
266 CHAPTER 5. FILLING IN THE LAYOUT
color: #777;
}
.
.
.
footer {
.
.
.
color: #777;
}
In this case, #777 is a light gray, and we can give it a name by defining a variable
as follows:
$light-gray: #777;
This allows us to rewrite our SCSS like this:
$light-gray: #777;
.
.
.
h2 {
.
.
.
color: $light-gray;
}
.
.
.
footer {
.
.
.
color: $light-gray;
}
Because variable names such as $light-gray are more descriptive than
#777, its often useful to define variables even for values that aren’t repeated.
Indeed, the Bootstrap framework defines a large number of variables for colors,
5.2. SASS AND THE ASSET PIPELINE 267
available online on the Bootstrap page of Less variables. That page defines
variables using Less, not Sass, but the bootstrap-sass gem provides the
Sass equivalents. It is not difficult to guess the correspondence; where Less uses
an “at” sign @, Sass uses a dollar sign $. For example, looking at the Bootstrap
variable page, we see that there is a variable for light gray:
@gray-light: #777;
This means that, via the bootstrap-sass gem, there should be a corre-
sponding SCSS variable $gray-light. We can use this to replace our custom
variable, $light-gray, which gives
h2 {
.
.
.
color: $gray-light;
}
.
.
.
footer {
.
.
.
color: $gray-light;
}
Applying the Sass nesting and variable definition features to the full SCSS
file gives the file in Listing 5.20. This uses both Sass variables (as inferred
from the Bootstrap Less variable page) and built-in named colors (i.e., white
for #fff). Note in particular the dramatic improvement in the rules for the
footer tag.
Listing 5.20: The initial SCSS file converted to use nesting and variables.
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";
268 CHAPTER 5. FILLING IN THE LAYOUT
/* mixins, variables, etc. */
$gray-medium-light: #eaeaea;
/* universal */
body {
padding-top: 60px;
}
section {
overflow: auto;
}
textarea {
resize: vertical;
}
.center {
text-align: center;
h1 {
margin-bottom: 10px;
}
}
/* typography */
h1, h2, h3, h4, h5, h6 {
line-height: 1;
}
h1 {
font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;
}
h2 {
font-size: 1.2em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: $gray-light;
}
p {
font-size: 1.1em;
line-height: 1.7em;
}
5.2. SASS AND THE ASSET PIPELINE 269
/* header */
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: white;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
&:hover {
color: white;
text-decoration: none;
}
}
/* footer */
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid $gray-medium-light;
color: $gray-light;
a {
color: $gray;
&:hover {
color: $gray-darker;
}
}
small {
float: left;
}
ul {
float: right;
list-style: none;
li {
float: left;
margin-left: 15px;
}
}
}
Sass gives us even more ways to simplify our stylesheets, but the code in
Listing 5.20 uses the most important features and gives us a great start. See the
Sass website for more details.
270 CHAPTER 5. FILLING IN THE LAYOUT
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. As suggested in Section 5.2.2, go through the steps to convert the footer
CSS from Listing 5.17 to Listing 5.20 to SCSS by hand.
5.3 Layout links
Now that we’ve finished a site layout with decent styling, it’s time to start filling
in the links we’ve stubbed out with '#'. Because plain HTML is valid in Rails
ERb templates, we could hard-code links like
<a href="/static_pages/about">About</a>
but that isn’t the Rails Way™. For one, it would be nice if the URL for the
about page were /about rather than /static_pages/about. Moreover, Rails con-
ventionally uses named routes, which involves code like
<%= link_to "About", about_path %>
This way the code has a more transparent meaning, and it’s also more flexible
since we can change the definition of about_path and have the URL change
everywhere about_path is used.
The full list of our planned links appears in Table 5.1, along with their map-
ping to URLs and routes. We took care of the first route in Section 3.4.4, and
we’ll have implemented all but the last one by the end of this chapter. (We’ll
make the last one in Chapter 8.)
5.3. LAYOUT LINKS 271
Page URL Named route
Home / root_path
About /about about_path
Help /help help_path
Contact /contact contact_path
Sign up /signup signup_path
Log in /login login_path
Table 5.1: Route and URL mapping for site links.
5.3.1 Contact page
For completeness, we’ll add the Contact page, which was left as an exercise in
Chapter 3. The test appears as in Listing 5.21, which simply follows the model
last seen in Listing 3.26.
Listing 5.21: A test for the Contact page. red
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Ruby on Rails Tutorial Sample App"
end
test "should get help" do
get static_pages_help_url
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
test "should get about" do
get static_pages_about_url
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
test "should get contact" do
get static_pages_contact_url
assert_response :success
assert_select "title", "Contact | Ruby on Rails Tutorial Sample App"
272 CHAPTER 5. FILLING IN THE LAYOUT
end
end
At this point, the tests in Listing 5.21 should be red:
Listing 5.22: red
$ rails test
The application code parallels the addition of the About page in Section 3.3:
first we update the routes (Listing 5.23), then we add a contact action to the
Static Pages controller (Listing 5.24), and finally we create a Contact view (List-
ing 5.25).
Listing 5.23: Adding a route for the Contact page. red
config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
get 'static_pages/contact'
end
Listing 5.24: Adding an action for the Contact page. red
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
.
.
.
def contact
end
end
5.3. LAYOUT LINKS 273
Listing 5.25: The view for the Contact page. green
app/views/static_pages/contact.html.erb
<% provide(:title, 'Contact') %>
<h1>Contact</h1>
<p>
Contact the Ruby on Rails Tutorial about the sample app at the
<a href="https://www.railstutorial.org/contact">contact page</a>.
</p>
Now make sure that the tests are green:
Listing 5.26: green
$ rails test
5.3.2 Rails routes
To add the named routes for the sample app’s static pages, we’ll edit the routes
file, config/routes.rb, that Rails uses to define URL mappings. We’ll be-
gin by reviewing the route for the Home page (defined in Section 3.4.4), which
is a special case, and then define a set of routes for the remaining static pages.
So far, we’ve seen three examples of how to define a root route, starting
with the code
root 'application#hello'
in the hello app (Listing 1.11), the code
root 'users#index'
in the toy app (Listing 2.7), and the code
274 CHAPTER 5. FILLING IN THE LAYOUT
root 'static_pages#home'
in the sample app (Listing 3.43). In each case, the root method arranges for
the root path / to be routed to a controller and action of our choice. Defining the
root route in this way has a second important effect, which is to create named
routes that allow us to refer to routes by a name rather than by the raw URL. In
this case, these routes are root_path and root_url, with the only difference
being that the latter includes the full URL:
root_path -> '/'
root_url -> 'http://www.example.com/'
In the Rails Tutorial, we’ll follow the common convention of using the _path
form except when doing redirects, where we’ll use the _url form. (This is be-
cause the HTTP standard technically requires a full URL after redirects, though
in most browsers it will work either way.)
Because the default routes used in, e.g., Listing 5.21 are rather verbose,
we’ll also take this opportunity to define shorter named routes for the Help,
About, and Contact pages. To do this, we need to make changes to the get
rules from Listing 5.23, transforming lines like
get 'static_pages/help'
to
get '/help', to: 'static_pages#help'
This new pattern routes a GET request for the URL /help to the help action in
the Static Pages controller. As with the rule for the root route, this creates two
named routes, help_path and help_url:
5.3. LAYOUT LINKS 275
help_path -> '/help'
help_url -> 'http://www.example.com/help'
Applying this rule change to the remaining static page routes from Listing 5.23
gives Listing 5.27.
Listing 5.27: Routes for static pages. red
config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
end
Note that Listing 5.27 also removes the route for 'static_pages/home', as
we’ll always use root_path or root_url instead.
Because the tests in Listing 5.21 used the old routes, they are now red. To
get them green again, we need to update the routes as shown in Listing 5.28.
Note that we’ve taken this opportunity to update to the (optional) convention
of using the *_path form of each named route.
Listing 5.28: The static pages tests with the new named routes. green
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get root_path
assert_response :success
assert_select "title", "Ruby on Rails Tutorial Sample App"
end
test "should get help" do
get help_path
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
276 CHAPTER 5. FILLING IN THE LAYOUT
end
test "should get about" do
get about_path
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
test "should get contact" do
get contact_path
assert_response :success
assert_select "title", "Contact | Ruby on Rails Tutorial Sample App"
end
end
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. It’s possible to use a named route other than the default using the as: op-
tion. Drawing inspiration from this famous Far Side comic strip, change
the route for the Help page to use helf (Listing 5.29).
2. Confirm that the tests are now red. Get them to green by updating the
route in Listing 5.28.
3. Revert the changes from these exercises using Undo.
Listing 5.29: Changing ‘help to ‘helf’.
Rails.application.routes.draw do
root 'static_pages#home'
get '/help', to: 'static_pages#help', as: 'helf'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
end
5.3. LAYOUT LINKS 277
5.3.3 Using named routes
With the routes defined in Listing 5.27, we’re now in a position to use the result-
ing named routes in the site layout. This simply involves filling in the second
arguments of the link_to functions with the proper named routes. For exam-
ple, we’ll convert
<%= link_to "About", '#' %>
to
<%= link_to "About", about_path %>
and so on.
We’ll start in the header partial, _header.html.erb (Listing 5.30), which
has links to the Home and Help pages. While we’re at it, we’ll follow a common
web convention and link the logo to the Home page as well.
Listing 5.30: Header partial with links.
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
We won’t have a named route for the “Log in link until Chapter 8, so we’ve
left it as '#' for now.
The other place with links is the footer partial, _footer.html.erb,
which has links for the About and Contact pages (Listing 5.31).
278 CHAPTER 5. FILLING IN THE LAYOUT
Listing 5.31: Footer partial with links.
app/views/layouts/_footer.html.erb
<footer class="footer">
<small>
The <a href="https://www.railstutorial.org/">Ruby on Rails Tutorial</a>
by <a href="https://www.michaelhartl.com/">Michael Hartl</a>
</small>
<nav>
<ul>
<li><%= link_to "About", about_path %></li>
<li><%= link_to "Contact", contact_path %></li>
<li><a href="https://news.railstutorial.org/">News</a></li>
</ul>
</nav>
</footer>
With that, our layout has links to all the static pages created in Chapter 3,
so that, for example, /about goes to the About page (Figure 5.10).
Exercises
Solutions to the exercises are available to all Rails Tutorial purchasers here.
To see other people’s answers and to record your own, subscribe to the Rails
Tutorial course or to the Learn Enough All Access Bundle.
1. Update the layout links to use the helf route from Listing 5.29.
2. Revert the changes using Undo.
5.3.4 Layout link tests
Now that we’ve filled in several of the layout links, it’s a good idea to test
them to make sure they’re working correctly. We could do this by hand with
a browser, first visiting the root path and then checking the links by hand, but
this quickly becomes cumbersome. Instead, we’ll simulate the same series of
steps using an integration test, which allows us to write an end-to-end test of
our application’s behavior. We can get started by generating a template test,
which we’ll call site_layout: